From 8da9db9f733db648d3c593b15a3683fef1327dec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 29 Aug 2024 00:54:55 +0000 Subject: [PATCH] Added chart versions: external-secrets/external-secrets: - 0.10.2 jenkins/jenkins: - 5.5.12 jfrog/artifactory-ha: - 107.90.9 jfrog/artifactory-jcr: - 107.90.9 kubecost/cost-analyzer: - 2.3.5 nats/nats: - 1.2.3 speedscale/speedscale-operator: - 2.2.314 --- .../external-secrets-0.10.2.tgz | Bin 0 -> 84898 bytes assets/jenkins/jenkins-5.5.12.tgz | Bin 0 -> 77823 bytes assets/jfrog/artifactory-ha-107.90.9.tgz | Bin 0 -> 218412 bytes assets/jfrog/artifactory-jcr-107.90.9.tgz | Bin 0 -> 459462 bytes assets/kubecost/cost-analyzer-2.3.4.tgz | Bin 146322 -> 146311 bytes assets/kubecost/cost-analyzer-2.3.5.tgz | Bin 0 -> 146470 bytes assets/nats/nats-1.2.3.tgz | Bin 0 -> 20135 bytes .../speedscale-operator-2.2.314.tgz | Bin 0 -> 16745 bytes .../external-secrets/0.10.2/Chart.lock | 6 + .../external-secrets/0.10.2/Chart.yaml | 25 + .../external-secrets/0.10.2/README.md | 225 + .../external-secrets/0.10.2/app-readme.md | 7 + .../charts/bitwarden-sdk-server/.helmignore | 23 + .../charts/bitwarden-sdk-server/Chart.yaml | 6 + .../bitwarden-sdk-server/templates/NOTES.txt | 22 + .../templates/_helpers.tpl | 62 + .../templates/deployment.yaml | 75 + .../templates/service.yaml | 14 + .../templates/serviceaccount.yaml | 12 + .../templates/tests/test-connection.yaml | 15 + .../charts/bitwarden-sdk-server/values.yaml | 98 + .../external-secrets/0.10.2/questions.yaml | 8 + .../0.10.2/templates/NOTES.txt | 7 + .../0.10.2/templates/_helpers.tpl | 198 + .../templates/cert-controller-deployment.yaml | 124 + .../cert-controller-poddisruptionbudget.yaml | 19 + .../templates/cert-controller-rbac.yaml | 86 + .../templates/cert-controller-service.yaml | 28 + .../cert-controller-serviceaccount.yaml | 16 + .../0.10.2/templates/crds/acraccesstoken.yaml | 203 + .../templates/crds/clusterexternalsecret.yaml | 666 ++ .../templates/crds/clustersecretstore.yaml | 4601 +++++++++++++ .../templates/crds/ecrauthorizationtoken.yaml | 177 + .../0.10.2/templates/crds/externalsecret.yaml | 820 +++ .../0.10.2/templates/crds/fake.yaml | 86 + .../0.10.2/templates/crds/gcraccesstoken.yaml | 138 + .../templates/crds/githubaccesstoken.yaml | 112 + .../0.10.2/templates/crds/password.yaml | 108 + .../0.10.2/templates/crds/pushsecret.yaml | 386 ++ .../0.10.2/templates/crds/secretstore.yaml | 4601 +++++++++++++ .../templates/crds/vaultdynamicsecret.yaml | 707 ++ .../0.10.2/templates/crds/webhook.yaml | 157 + .../0.10.2/templates/deployment.yaml | 146 + .../0.10.2/templates/extra-manifests.yaml | 4 + .../0.10.2/templates/poddisruptionbudget.yaml | 19 + .../0.10.2/templates/rbac.yaml | 301 + .../0.10.2/templates/service.yaml | 28 + .../0.10.2/templates/serviceaccount.yaml | 16 + .../0.10.2/templates/servicemonitor.yaml | 164 + .../0.10.2/templates/validatingwebhook.yaml | 78 + .../0.10.2/templates/webhook-certificate.yaml | 30 + .../0.10.2/templates/webhook-deployment.yaml | 128 + .../webhook-poddisruptionbudget.yaml | 20 + .../0.10.2/templates/webhook-secret.yaml | 14 + .../0.10.2/templates/webhook-service.yaml | 37 + .../templates/webhook-serviceaccount.yaml | 16 + .../external-secrets/0.10.2/values.yaml | 532 ++ charts/jenkins/jenkins/5.5.12/CHANGELOG.md | 3090 +++++++++ charts/jenkins/jenkins/5.5.12/Chart.yaml | 54 + charts/jenkins/jenkins/5.5.12/README.md | 706 ++ charts/jenkins/jenkins/5.5.12/UPGRADING.md | 148 + charts/jenkins/jenkins/5.5.12/VALUES.md | 316 + .../jenkins/jenkins/5.5.12/VALUES.md.gotmpl | 28 + .../jenkins/5.5.12/templates/NOTES.txt | 68 + .../jenkins/5.5.12/templates/_helpers.tpl | 684 ++ .../5.5.12/templates/auto-reload-config.yaml | 60 + .../5.5.12/templates/config-init-scripts.yaml | 18 + .../jenkins/5.5.12/templates/config.yaml | 92 + .../jenkins/5.5.12/templates/deprecation.yaml | 151 + .../jenkins/5.5.12/templates/home-pvc.yaml | 41 + .../5.5.12/templates/jcasc-config.yaml | 53 + .../5.5.12/templates/jenkins-agent-svc.yaml | 43 + .../jenkins-aws-security-group-policies.yaml | 16 + .../jenkins-controller-alerting-rules.yaml | 26 + .../jenkins-controller-backendconfig.yaml | 24 + .../templates/jenkins-controller-ingress.yaml | 77 + .../jenkins-controller-networkpolicy.yaml | 76 + .../templates/jenkins-controller-pdb.yaml | 34 + .../jenkins-controller-podmonitor.yaml | 30 + .../templates/jenkins-controller-route.yaml | 34 + .../jenkins-controller-secondary-ingress.yaml | 56 + .../jenkins-controller-servicemonitor.yaml | 45 + .../jenkins-controller-statefulset.yaml | 424 ++ .../templates/jenkins-controller-svc.yaml | 56 + .../jenkins/5.5.12/templates/rbac.yaml | 149 + .../5.5.12/templates/secret-additional.yaml | 21 + .../5.5.12/templates/secret-claims.yaml | 29 + .../5.5.12/templates/secret-https-jks.yaml | 20 + .../jenkins/5.5.12/templates/secret.yaml | 20 + .../templates/service-account-agent.yaml | 26 + .../5.5.12/templates/service-account.yaml | 26 + .../5.5.12/templates/tests/jenkins-test.yaml | 49 + .../5.5.12/templates/tests/test-config.yaml | 14 + charts/jenkins/jenkins/5.5.12/values.yaml | 1357 ++++ .../jfrog/artifactory-ha/107.90.9/.helmignore | 24 + .../artifactory-ha/107.90.9/CHANGELOG.md | 1466 +++++ .../jfrog/artifactory-ha/107.90.9/Chart.lock | 6 + .../jfrog/artifactory-ha/107.90.9/Chart.yaml | 30 + charts/jfrog/artifactory-ha/107.90.9/LICENSE | 201 + .../jfrog/artifactory-ha/107.90.9/README.md | 69 + .../artifactory-ha/107.90.9/app-readme.md | 16 + .../107.90.9/charts/postgresql/.helmignore | 21 + .../107.90.9/charts/postgresql/Chart.lock | 6 + .../107.90.9/charts/postgresql/Chart.yaml | 29 + .../107.90.9/charts/postgresql/README.md | 770 +++ .../postgresql/charts/common/.helmignore | 22 + .../postgresql/charts/common/Chart.yaml | 23 + .../charts/postgresql/charts/common/README.md | 322 + .../charts/common/templates/_affinities.tpl | 94 + .../charts/common/templates/_capabilities.tpl | 95 + .../charts/common/templates/_errors.tpl | 23 + .../charts/common/templates/_images.tpl | 47 + .../charts/common/templates/_ingress.tpl | 42 + .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 32 + .../charts/common/templates/_secrets.tpl | 129 + .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 62 + .../charts/common/templates/_warnings.tpl | 14 + .../templates/validations/_cassandra.tpl | 72 + .../common/templates/validations/_mariadb.tpl | 103 + .../common/templates/validations/_mongodb.tpl | 108 + .../templates/validations/_postgresql.tpl | 131 + .../common/templates/validations/_redis.tpl | 72 + .../templates/validations/_validations.tpl | 46 + .../postgresql/charts/common/values.yaml | 3 + .../postgresql/ci/commonAnnotations.yaml | 3 + .../charts/postgresql/ci/default-values.yaml | 1 + .../ci/shmvolume-disabled-values.yaml | 2 + .../charts/postgresql/files/README.md | 1 + .../charts/postgresql/files/conf.d/README.md | 4 + .../docker-entrypoint-initdb.d/README.md | 3 + .../charts/postgresql/templates/NOTES.txt | 59 + .../charts/postgresql/templates/_helpers.tpl | 337 + .../postgresql/templates/configmap.yaml | 31 + .../templates/extended-config-configmap.yaml | 26 + .../postgresql/templates/extra-list.yaml | 4 + .../templates/initialization-configmap.yaml | 25 + .../templates/metrics-configmap.yaml | 14 + .../postgresql/templates/metrics-svc.yaml | 26 + .../postgresql/templates/networkpolicy.yaml | 39 + .../templates/podsecuritypolicy.yaml | 38 + .../postgresql/templates/prometheusrule.yaml | 23 + .../charts/postgresql/templates/role.yaml | 20 + .../postgresql/templates/rolebinding.yaml | 20 + .../charts/postgresql/templates/secrets.yaml | 24 + .../postgresql/templates/serviceaccount.yaml | 12 + .../postgresql/templates/servicemonitor.yaml | 33 + .../templates/statefulset-readreplicas.yaml | 411 ++ .../postgresql/templates/statefulset.yaml | 609 ++ .../postgresql/templates/svc-headless.yaml | 28 + .../charts/postgresql/templates/svc-read.yaml | 43 + .../charts/postgresql/templates/svc.yaml | 41 + .../charts/postgresql/values.schema.json | 103 + .../107.90.9/charts/postgresql/values.yaml | 824 +++ .../107.90.9/ci/access-tls-values.yaml | 34 + .../107.90.9/ci/default-values.yaml | 32 + .../107.90.9/ci/global-values.yaml | 255 + .../107.90.9/ci/large-values.yaml | 85 + .../107.90.9/ci/loggers-values.yaml | 43 + .../107.90.9/ci/medium-values.yaml | 85 + .../ci/migration-disabled-values.yaml | 31 + .../107.90.9/ci/nginx-autoreload-values.yaml | 53 + .../ci/rtsplit-access-tls-values.yaml | 106 + .../107.90.9/ci/rtsplit-values.yaml | 155 + .../107.90.9/ci/small-values.yaml | 87 + .../107.90.9/ci/test-values.yaml | 85 + .../107.90.9/files/binarystore.xml | 439 ++ .../107.90.9/files/installer-info.json | 32 + .../artifactory-ha/107.90.9/files/migrate.sh | 4311 +++++++++++++ .../107.90.9/files/migrationHelmInfo.yaml | 27 + .../107.90.9/files/migrationStatus.sh | 44 + .../files/nginx-artifactory-conf.yaml | 98 + .../107.90.9/files/nginx-main-conf.yaml | 83 + .../artifactory-ha/107.90.9/files/system.yaml | 163 + .../107.90.9/logo/artifactory-logo.png | Bin 0 -> 82419 bytes .../artifactory-ha/107.90.9/questions.yml | 424 ++ .../artifactory-2xlarge-extra-config.yaml | 44 + .../107.90.9/sizing/artifactory-2xlarge.yaml | 127 + .../artifactory-large-extra-config.yaml | 44 + .../107.90.9/sizing/artifactory-large.yaml | 127 + .../artifactory-medium-extra-config.yaml | 45 + .../107.90.9/sizing/artifactory-medium.yaml | 127 + .../artifactory-small-extra-config.yaml | 43 + .../107.90.9/sizing/artifactory-small.yaml | 127 + .../artifactory-xlarge-extra-config.yaml | 42 + .../107.90.9/sizing/artifactory-xlarge.yaml | 127 + .../artifactory-xsmall-extra-config.yaml | 43 + .../107.90.9/sizing/artifactory-xsmall.yaml | 127 + .../107.90.9/templates/NOTES.txt | 149 + .../107.90.9/templates/_helpers.tpl | 563 ++ .../templates/_system-yaml-render.tpl | 5 + .../templates/additional-resources.yaml | 3 + .../templates/admin-bootstrap-creds.yaml | 15 + .../templates/artifactory-access-config.yaml | 15 + .../artifactory-binarystore-secret.yaml | 18 + .../templates/artifactory-configmaps.yaml | 13 + .../templates/artifactory-custom-secrets.yaml | 19 + .../artifactory-database-secrets.yaml | 24 + .../artifactory-gcp-credentials-secret.yaml | 16 + .../templates/artifactory-installer-info.yaml | 16 + .../templates/artifactory-license-secret.yaml | 16 + .../artifactory-migration-scripts.yaml | 18 + .../templates/artifactory-networkpolicy.yaml | 34 + .../templates/artifactory-nfs-pvc.yaml | 101 + .../templates/artifactory-node-pdb.yaml | 26 + .../artifactory-node-statefulset.yaml | 1588 +++++ .../templates/artifactory-primary-pdb.yaml | 24 + .../artifactory-primary-service.yaml | 57 + .../artifactory-primary-statefulset.yaml | 1717 +++++ .../templates/artifactory-priority-class.yaml | 9 + .../107.90.9/templates/artifactory-role.yaml | 14 + .../templates/artifactory-rolebinding.yaml | 19 + .../templates/artifactory-secrets.yaml | 30 + .../templates/artifactory-service.yaml | 72 + .../templates/artifactory-serviceaccount.yaml | 17 + .../templates/artifactory-storage-pvc.yaml | 27 + .../templates/artifactory-system-yaml.yaml | 16 + .../templates/artifactory-unified-secret.yaml | 96 + .../templates/filebeat-configmap.yaml | 15 + .../107.90.9/templates/ingress.yaml | 106 + .../107.90.9/templates/logger-configmap.yaml | 63 + .../templates/nginx-artifactory-conf.yaml | 18 + .../templates/nginx-certificate-secret.yaml | 14 + .../107.90.9/templates/nginx-conf.yaml | 18 + .../107.90.9/templates/nginx-deployment.yaml | 221 + .../107.90.9/templates/nginx-pdb.yaml | 23 + .../107.90.9/templates/nginx-pvc.yaml | 26 + .../templates/nginx-scripts-conf.yaml | 52 + .../107.90.9/templates/nginx-service.yaml | 94 + .../jfrog/artifactory-ha/107.90.9/values.yaml | 1834 ++++++ .../artifactory-jcr/107.90.9/CHANGELOG.md | 206 + .../jfrog/artifactory-jcr/107.90.9/Chart.yaml | 30 + charts/jfrog/artifactory-jcr/107.90.9/LICENSE | 201 + .../jfrog/artifactory-jcr/107.90.9/README.md | 125 + .../artifactory-jcr/107.90.9/app-readme.md | 18 + .../107.90.9/charts/artifactory/.helmignore | 24 + .../107.90.9/charts/artifactory/CHANGELOG.md | 1365 ++++ .../107.90.9/charts/artifactory/Chart.lock | 6 + .../107.90.9/charts/artifactory/Chart.yaml | 24 + .../107.90.9/charts/artifactory/LICENSE | 201 + .../107.90.9/charts/artifactory/README.md | 59 + .../artifactory/charts/postgresql/.helmignore | 21 + .../artifactory/charts/postgresql/Chart.lock | 6 + .../artifactory/charts/postgresql/Chart.yaml | 29 + .../artifactory/charts/postgresql/README.md | 770 +++ .../postgresql/charts/common/.helmignore | 22 + .../postgresql/charts/common/Chart.yaml | 23 + .../charts/postgresql/charts/common/README.md | 322 + .../charts/common/templates/_affinities.tpl | 94 + .../charts/common/templates/_capabilities.tpl | 95 + .../charts/common/templates/_errors.tpl | 23 + .../charts/common/templates/_images.tpl | 47 + .../charts/common/templates/_ingress.tpl | 42 + .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 32 + .../charts/common/templates/_secrets.tpl | 129 + .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 62 + .../charts/common/templates/_warnings.tpl | 14 + .../templates/validations/_cassandra.tpl | 72 + .../common/templates/validations/_mariadb.tpl | 103 + .../common/templates/validations/_mongodb.tpl | 108 + .../templates/validations/_postgresql.tpl | 131 + .../common/templates/validations/_redis.tpl | 72 + .../templates/validations/_validations.tpl | 46 + .../postgresql/charts/common/values.yaml | 3 + .../postgresql/ci/commonAnnotations.yaml | 3 + .../charts/postgresql/ci/default-values.yaml | 1 + .../ci/shmvolume-disabled-values.yaml | 2 + .../charts/postgresql/files/README.md | 1 + .../charts/postgresql/files/conf.d/README.md | 4 + .../docker-entrypoint-initdb.d/README.md | 3 + .../charts/postgresql/templates/NOTES.txt | 59 + .../charts/postgresql/templates/_helpers.tpl | 337 + .../postgresql/templates/configmap.yaml | 31 + .../templates/extended-config-configmap.yaml | 26 + .../postgresql/templates/extra-list.yaml | 4 + .../templates/initialization-configmap.yaml | 25 + .../templates/metrics-configmap.yaml | 14 + .../postgresql/templates/metrics-svc.yaml | 26 + .../postgresql/templates/networkpolicy.yaml | 39 + .../templates/podsecuritypolicy.yaml | 38 + .../postgresql/templates/prometheusrule.yaml | 23 + .../charts/postgresql/templates/role.yaml | 20 + .../postgresql/templates/rolebinding.yaml | 20 + .../charts/postgresql/templates/secrets.yaml | 24 + .../postgresql/templates/serviceaccount.yaml | 12 + .../postgresql/templates/servicemonitor.yaml | 33 + .../templates/statefulset-readreplicas.yaml | 411 ++ .../postgresql/templates/statefulset.yaml | 609 ++ .../postgresql/templates/svc-headless.yaml | 28 + .../charts/postgresql/templates/svc-read.yaml | 43 + .../charts/postgresql/templates/svc.yaml | 41 + .../charts/postgresql/values.schema.json | 103 + .../artifactory/charts/postgresql/values.yaml | 824 +++ .../artifactory/ci/access-tls-values.yaml | 24 + .../charts/artifactory/ci/default-values.yaml | 21 + .../artifactory/ci/derby-test-values.yaml | 19 + .../charts/artifactory/ci/global-values.yaml | 247 + .../charts/artifactory/ci/large-values.yaml | 82 + .../charts/artifactory/ci/loggers-values.yaml | 43 + .../charts/artifactory/ci/medium-values.yaml | 82 + .../ci/migration-disabled-values.yaml | 21 + .../ci/nginx-autoreload-values.yaml | 42 + .../ci/rtsplit-values-access-tls-values.yaml | 96 + .../charts/artifactory/ci/rtsplit-values.yaml | 151 + .../charts/artifactory/ci/small-values.yaml | 82 + .../charts/artifactory/ci/test-values.yaml | 84 + .../charts/artifactory/files/binarystore.xml | 426 ++ .../artifactory/files/installer-info.json | 32 + .../charts/artifactory/files/migrate.sh | 4311 +++++++++++++ .../artifactory/files/migrationHelmInfo.yaml | 27 + .../artifactory/files/migrationStatus.sh | 44 + .../files/nginx-artifactory-conf.yaml | 98 + .../artifactory/files/nginx-main-conf.yaml | 83 + .../charts/artifactory/files/system.yaml | 156 + .../artifactory/logo/artifactory-logo.png | Bin 0 -> 82419 bytes .../artifactory-2xlarge-extra-config.yaml | 41 + .../sizing/artifactory-2xlarge.yaml | 126 + .../artifactory-large-extra-config.yaml | 41 + .../artifactory/sizing/artifactory-large.yaml | 126 + .../artifactory-medium-extra-config.yaml | 41 + .../sizing/artifactory-medium.yaml | 126 + .../artifactory-small-extra-config.yaml | 41 + .../artifactory/sizing/artifactory-small.yaml | 124 + .../artifactory-xlarge-extra-config.yaml | 41 + .../sizing/artifactory-xlarge.yaml | 126 + .../artifactory-xsmall-extra-config.yaml | 42 + .../sizing/artifactory-xsmall.yaml | 125 + .../charts/artifactory/templates/NOTES.txt | 106 + .../charts/artifactory/templates/_helpers.tpl | 528 ++ .../templates/_system-yaml-render.tpl | 5 + .../templates/additional-resources.yaml | 3 + .../templates/admin-bootstrap-creds.yaml | 15 + .../templates/artifactory-access-config.yaml | 15 + .../artifactory-binarystore-secret.yaml | 18 + .../templates/artifactory-configmaps.yaml | 13 + .../templates/artifactory-custom-secrets.yaml | 19 + .../artifactory-database-secrets.yaml | 24 + .../artifactory-gcp-credentials-secret.yaml | 16 + .../templates/artifactory-hpa.yaml | 29 + .../templates/artifactory-installer-info.yaml | 16 + .../templates/artifactory-license-secret.yaml | 16 + .../artifactory-migration-scripts.yaml | 18 + .../templates/artifactory-networkpolicy.yaml | 34 + .../templates/artifactory-nfs-pvc.yaml | 101 + .../templates/artifactory-pdb.yaml | 24 + .../templates/artifactory-priority-class.yaml | 9 + .../templates/artifactory-role.yaml | 14 + .../templates/artifactory-rolebinding.yaml | 19 + .../templates/artifactory-secrets.yaml | 30 + .../templates/artifactory-service.yaml | 63 + .../templates/artifactory-serviceaccount.yaml | 17 + .../templates/artifactory-statefulset.yaml | 1633 +++++ .../templates/artifactory-system-yaml.yaml | 16 + .../templates/artifactory-unified-secret.yaml | 94 + .../templates/filebeat-configmap.yaml | 15 + .../charts/artifactory/templates/ingress.yaml | 109 + .../templates/logger-configmap.yaml | 63 + .../templates/nginx-artifactory-conf.yaml | 18 + .../templates/nginx-certificate-secret.yaml | 14 + .../artifactory/templates/nginx-conf.yaml | 18 + .../templates/nginx-deployment.yaml | 223 + .../artifactory/templates/nginx-pdb.yaml | 23 + .../artifactory/templates/nginx-pvc.yaml | 26 + .../templates/nginx-scripts-conf.yaml | 52 + .../artifactory/templates/nginx-service.yaml | 88 + .../107.90.9/charts/artifactory/values.yaml | 1749 +++++ .../107.90.9/ci/default-values.yaml | 7 + .../107.90.9/logo/jcr-logo.png | Bin 0 -> 77047 bytes .../artifactory-jcr/107.90.9/questions.yml | 271 + .../107.90.9/templates/NOTES.txt | 1 + .../artifactory-jcr/107.90.9/values.yaml | 75 + .../kubecost/cost-analyzer/2.3.4/Chart.yaml | 1 - .../kubecost/cost-analyzer/2.3.5/Chart.yaml | 14 + charts/kubecost/cost-analyzer/2.3.5/README.md | 116 + .../cost-analyzer/2.3.5/app-readme.md | 25 + .../2.3.5/ci/aggregator-values.yaml | 17 + .../federatedetl-primary-netcosts-values.yaml | 35 + .../2.3.5/ci/statefulsets-cc.yaml | 46 + .../2.3.5/crds/cluster-turndown-crd.yaml | 78 + .../cost-analyzer/2.3.5/custom-pricing.csv | 7 + .../2.3.5/grafana-dashboards/README.md | 45 + .../grafana-dashboards/attached-disks.json | 549 ++ .../grafana-dashboards/cluster-metrics.json | 1683 +++++ .../cluster-utilization.json | 3196 +++++++++ .../deployment-utilization.json | 1386 ++++ .../aggregator-dashboard.json | 668 ++ .../multi-cluster-container-stats.json | 787 +++ .../multi-cluster-disk-usage.json | 571 ++ .../multi-cluster-network-transfer-data.json | 685 ++ .../kubernetes-resource-efficiency.json | 408 ++ .../label-cost-utilization.json | 1146 ++++ .../namespace-utilization.json | 1175 ++++ .../network-cloud-services.json | 408 ++ .../networkCosts-metrics.json | 672 ++ .../grafana-dashboards/node-utilization.json | 1389 ++++ .../pod-utilization-multi-cluster.json | 788 +++ .../grafana-dashboards/pod-utilization.json | 757 +++ .../grafana-dashboards/prom-benchmark.json | 5691 +++++++++++++++++ .../workload-metrics-aggregator.json | 988 +++ .../grafana-dashboards/workload-metrics.json | 893 +++ .../cost-analyzer/2.3.5/questions.yaml | 187 + .../create-admission-controller-tls.sh | 29 + .../cost-analyzer/2.3.5/templates/NOTES.txt | 27 + .../2.3.5/templates/_helpers.tpl | 1465 +++++ .../aggregator-cloud-cost-deployment.yaml | 167 + ...aggregator-cloud-cost-service-account.yaml | 23 + .../aggregator-cloud-cost-service.yaml | 17 + .../2.3.5/templates/aggregator-service.yaml | 25 + .../templates/aggregator-servicemonitor.yaml | 31 + .../templates/aggregator-statefulset.yaml | 207 + .../templates/alibaba-service-key-secret.yaml | 20 + .../templates/aws-service-key-secret.yaml | 20 + .../awsstore-deployment-template.yaml | 49 + .../awsstore-service-account-template.yaml | 15 + .../templates/azure-service-key-secret.yaml | 24 + .../azure-storage-config-secret.yaml | 26 + .../templates/cloud-integration-secret.yaml | 16 + ...st-analyzer-account-mapping-configmap.yaml | 12 + ...t-analyzer-advanced-reports-configmap.yaml | 13 + .../cost-analyzer-alerts-configmap.yaml | 10 + ...cost-analyzer-asset-reports-configmap.yaml | 14 + ...analyzer-cloud-cost-reports-configmap.yaml | 13 + ...nalyzer-cluster-role-binding-template.yaml | 36 + ...alyzer-cluster-role-template-readonly.yaml | 26 + .../cost-analyzer-cluster-role-template.yaml | 108 + .../cost-analyzer-config-map-template.yaml | 35 + .../cost-analyzer-db-pvc-template.yaml | 35 + .../cost-analyzer-deployment-template.yaml | 1191 ++++ ...analyzer-frontend-config-map-template.yaml | 1328 ++++ .../cost-analyzer-ingress-template.yaml | 56 + ...-analyzer-metrics-config-map-template.yaml | 13 + ...zer-network-costs-config-map-template.yaml | 16 + ...zer-network-costs-podmonitor-template.yaml | 32 + ...alyzer-network-costs-service-template.yaml | 34 + .../cost-analyzer-network-costs-template.yaml | 149 + ...cost-analyzer-network-policy-template.yaml | 47 + .../cost-analyzer-network-policy.yaml | 48 + .../cost-analyzer-networks-costs-ocp-scc.yaml | 30 + .../templates/cost-analyzer-ocp-route.yaml | 25 + ...ost-analyzer-oidc-config-map-template.yaml | 49 + .../cost-analyzer-pkey-configmap.yaml | 23 + .../cost-analyzer-pricing-configmap.yaml | 141 + ...cost-analyzer-prometheusrule-template.yaml | 22 + .../templates/cost-analyzer-pvc-template.yaml | 33 + ...ost-analyzer-saml-config-map-template.yaml | 14 + ...cost-analyzer-saved-reports-configmap.yaml | 13 + .../cost-analyzer-server-configmap.yaml | 72 + ...ost-analyzer-service-account-template.yaml | 13 + .../cost-analyzer-service-template.yaml | 66 + ...cost-analyzer-servicemonitor-template.yaml | 34 + .../cost-analyzer-smtp-configmap.yaml | 12 + .../templates/diagnostics-deployment.yaml | 177 + .../2.3.5/templates/diagnostics-service.yaml | 20 + .../2.3.5/templates/etl-utils-deployment.yaml | 123 + .../2.3.5/templates/etl-utils-service.yaml | 18 + .../external-grafana-config-map-template.yaml | 11 + .../2.3.5/templates/extra-manifests.yaml | 8 + .../templates/forecasting-deployment.yaml | 145 + .../2.3.5/templates/forecasting-service.yaml | 17 + .../frontend-deployment-template.yaml | 218 + .../templates/frontend-service-template.yaml | 53 + .../gcpstore-config-map-template.yaml | 61 + .../2.3.5/templates/grafana-clusterrole.yaml | 24 + .../templates/grafana-clusterrolebinding.yaml | 24 + .../grafana-configmap-dashboard-provider.yaml | 28 + .../2.3.5/templates/grafana-configmap.yaml | 90 + .../grafana-dashboard-attached-disks.yaml | 21 + ...na-dashboard-cluster-metrics-template.yaml | 21 + ...ashboard-cluster-utilization-template.yaml | 21 + ...board-deployment-utilization-template.yaml | 21 + ...bernetes-resource-efficiency-template.yaml | 21 + ...board-label-cost-utilization-template.yaml | 21 + ...hboard-namespace-utilization-template.yaml | 21 + ...afana-dashboard-network-cloud-sevices.yaml | 21 + .../grafana-dashboard-network-costs.yaml | 21 + ...a-dashboard-node-utilization-template.yaml | 21 + ...shboard-pod-utilization-multi-cluster.yaml | 21 + ...na-dashboard-pod-utilization-template.yaml | 21 + ...dashboard-prometheus-metrics-template.yaml | 21 + ...grafana-dashboard-workload-aggregator.yaml | 21 + .../grafana-dashboard-workload-metrics.yaml | 21 + .../grafana-dashboards-json-configmap.yaml | 24 + .../grafana-datasource-template.yaml | 38 + .../2.3.5/templates/grafana-deployment.yaml | 313 + .../2.3.5/templates/grafana-ingress.yaml | 47 + .../2.3.5/templates/grafana-pvc.yaml | 26 + .../2.3.5/templates/grafana-secret.yaml | 22 + .../2.3.5/templates/grafana-service.yaml | 51 + .../templates/grafana-serviceaccount.yaml | 13 + .../2.3.5/templates/install-plugins.yaml | 43 + ...tegrations-postgres-queries-configmap.yaml | 14 + .../integrations-postgres-secret.yaml | 19 + ...admission-controller-service-template.yaml | 15 + ...ubecost-admission-controller-template.yaml | 30 + .../kubecost-agent-secret-template.yaml | 12 + ...ubecost-agent-secretprovider-template.yaml | 25 + ...ost-cluster-controller-actions-config.yaml | 56 + .../kubecost-cluster-controller-template.yaml | 293 + ...st-cluster-manager-configmap-template.yaml | 14 + .../kubecost-metrics-deployment-template.yaml | 341 + ...cost-metrics-service-monitor-template.yaml | 41 + .../kubecost-metrics-service-template.yaml | 34 + .../kubecost-oidc-secret-template.yaml | 16 + .../kubecost-priority-class-template.yaml | 15 + .../kubecost-saml-secret-template.yaml | 12 + .../mimir-proxy-configmap-template.yaml | 21 + .../mimir-proxy-deployment-template.yaml | 46 + .../mimir-proxy-service-template.yaml | 18 + .../templates/model-ingress-template.yaml | 51 + ...network-costs-servicemonitor-template.yaml | 32 + .../2.3.5/templates/plugins-config.yaml | 13 + .../prometheus-alertmanager-configmap.yaml | 21 + .../prometheus-alertmanager-deployment.yaml | 148 + .../prometheus-alertmanager-ingress.yaml | 41 + ...prometheus-alertmanager-networkpolicy.yaml | 22 + .../prometheus-alertmanager-pdb.yaml | 16 + .../prometheus-alertmanager-pvc.yaml | 35 + ...metheus-alertmanager-service-headless.yaml | 33 + .../prometheus-alertmanager-service.yaml | 55 + ...rometheus-alertmanager-serviceaccount.yaml | 11 + .../prometheus-alertmanager-statefulset.yaml | 155 + .../prometheus-node-exporter-daemonset.yaml | 139 + .../prometheus-node-exporter-ocp-scc.yaml | 29 + .../prometheus-node-exporter-service.yaml | 47 + ...ometheus-node-exporter-serviceaccount.yaml | 11 + .../prometheus-pushgateway-deployment.yaml | 106 + .../prometheus-pushgateway-ingress.yaml | 38 + .../prometheus-pushgateway-networkpolicy.yaml | 22 + .../templates/prometheus-pushgateway-pdb.yaml | 15 + .../templates/prometheus-pushgateway-pvc.yaml | 35 + .../prometheus-pushgateway-service.yaml | 43 + ...prometheus-pushgateway-serviceaccount.yaml | 11 + .../prometheus-server-clusterrole.yaml | 39 + .../prometheus-server-clusterrolebinding.yaml | 18 + .../prometheus-server-configmap.yaml | 90 + .../prometheus-server-deployment.yaml | 265 + .../templates/prometheus-server-ingress.yaml | 45 + .../prometheus-server-networkpolicy.yaml | 16 + .../templates/prometheus-server-pdb.yaml | 15 + .../templates/prometheus-server-pvc.yaml | 37 + .../prometheus-server-service-headless.yaml | 29 + .../templates/prometheus-server-service.yaml | 62 + .../prometheus-server-serviceaccount.yaml | 17 + .../prometheus-server-statefulset.yaml | 227 + .../templates/prometheus-server-vpa.yaml | 22 + .../2.3.5/templates/tests/_helpers.tpl | 5 + .../2.3.5/templates/tests/basic-health.yaml | 48 + .../cost-analyzer/2.3.5/values-agent.yaml | 113 + .../cost-analyzer/2.3.5/values-amp.yaml | 20 + .../2.3.5/values-cloud-agent.yaml | 45 + .../2.3.5/values-custom-pricing.yaml | 17 + .../2.3.5/values-eks-cost-monitoring.yaml | 43 + .../cost-analyzer/2.3.5/values-openshift.yaml | 25 + .../2.3.5/values-windows-node-affinity.yaml | 30 + .../kubecost/cost-analyzer/2.3.5/values.yaml | 3456 ++++++++++ charts/nats/nats/1.2.3/.helmignore | 26 + charts/nats/nats/1.2.3/Chart.yaml | 22 + charts/nats/nats/1.2.3/README.md | 329 + charts/nats/nats/1.2.3/UPGRADING.md | 155 + charts/nats/nats/1.2.3/app-readme.md | 3 + charts/nats/nats/1.2.3/files/config-map.yaml | 10 + .../nats/nats/1.2.3/files/config/cluster.yaml | 32 + .../nats/nats/1.2.3/files/config/config.yaml | 114 + .../nats/nats/1.2.3/files/config/gateway.yaml | 11 + .../nats/1.2.3/files/config/jetstream.yaml | 23 + .../nats/1.2.3/files/config/leafnodes.yaml | 11 + charts/nats/nats/1.2.3/files/config/mqtt.yaml | 10 + .../nats/1.2.3/files/config/protocol.yaml | 10 + .../nats/1.2.3/files/config/resolver.yaml | 3 + charts/nats/nats/1.2.3/files/config/tls.yaml | 16 + .../nats/1.2.3/files/config/websocket.yaml | 12 + .../nats/1.2.3/files/headless-service.yaml | 24 + charts/nats/nats/1.2.3/files/ingress.yaml | 34 + .../1.2.3/files/nats-box/contents-secret.yaml | 17 + .../nats-box/contexts-secret/context.yaml | 51 + .../contexts-secret/contexts-secret.yaml | 13 + .../files/nats-box/deployment/container.yaml | 46 + .../files/nats-box/deployment/deployment.yaml | 16 + .../nats-box/deployment/pod-template.yaml | 44 + .../1.2.3/files/nats-box/service-account.yaml | 7 + .../1.2.3/files/pod-disruption-budget.yaml | 12 + charts/nats/nats/1.2.3/files/pod-monitor.yaml | 13 + .../nats/1.2.3/files/service-account.yaml | 7 + charts/nats/nats/1.2.3/files/service.yaml | 23 + .../files/stateful-set/jetstream-pvc.yaml | 13 + .../files/stateful-set/nats-container.yaml | 106 + .../files/stateful-set/pod-template.yaml | 71 + .../stateful-set/prom-exporter-container.yaml | 30 + .../stateful-set/reloader-container.yaml | 27 + .../files/stateful-set/resolver-pvc.yaml | 13 + .../files/stateful-set/stateful-set.yaml | 37 + charts/nats/nats/1.2.3/questions.yaml | 12 + charts/nats/nats/1.2.3/templates/_helpers.tpl | 281 + .../nats/nats/1.2.3/templates/_jsonpatch.tpl | 219 + .../nats/1.2.3/templates/_toPrettyRawJson.tpl | 28 + charts/nats/nats/1.2.3/templates/_tplYaml.tpl | 114 + .../nats/nats/1.2.3/templates/config-map.yaml | 4 + .../nats/1.2.3/templates/extra-resources.yaml | 5 + .../1.2.3/templates/headless-service.yaml | 4 + charts/nats/nats/1.2.3/templates/ingress.yaml | 6 + .../templates/nats-box/contents-secret.yaml | 10 + .../templates/nats-box/contexts-secret.yaml | 8 + .../1.2.3/templates/nats-box/deployment.yaml | 8 + .../templates/nats-box/service-account.yaml | 8 + .../templates/pod-disruption-budget.yaml | 6 + .../nats/1.2.3/templates/pod-monitor.yaml | 8 + .../nats/1.2.3/templates/service-account.yaml | 6 + charts/nats/nats/1.2.3/templates/service.yaml | 6 + .../nats/1.2.3/templates/stateful-set.yaml | 4 + .../1.2.3/templates/tests/request-reply.yaml | 37 + charts/nats/nats/1.2.3/values.yaml | 669 ++ .../speedscale-operator/2.2.314/.helmignore | 23 + .../speedscale-operator/2.2.314/Chart.yaml | 27 + .../speedscale-operator/2.2.314/LICENSE | 201 + .../speedscale-operator/2.2.314/README.md | 111 + .../speedscale-operator/2.2.314/app-readme.md | 111 + .../2.2.314/questions.yaml | 9 + .../2.2.314/templates/NOTES.txt | 12 + .../2.2.314/templates/admission.yaml | 199 + .../2.2.314/templates/configmap.yaml | 41 + .../templates/crds/trafficreplays.yaml | 515 ++ .../2.2.314/templates/deployments.yaml | 132 + .../2.2.314/templates/hooks.yaml | 73 + .../2.2.314/templates/rbac.yaml | 244 + .../2.2.314/templates/secrets.yaml | 18 + .../2.2.314/templates/services.yaml | 22 + .../2.2.314/templates/tls.yaml | 183 + .../speedscale-operator/2.2.314/values.yaml | 133 + index.yaml | 233 +- 634 files changed, 109782 insertions(+), 3 deletions(-) create mode 100644 assets/external-secrets/external-secrets-0.10.2.tgz create mode 100644 assets/jenkins/jenkins-5.5.12.tgz create mode 100644 assets/jfrog/artifactory-ha-107.90.9.tgz create mode 100644 assets/jfrog/artifactory-jcr-107.90.9.tgz create mode 100644 assets/kubecost/cost-analyzer-2.3.5.tgz create mode 100644 assets/nats/nats-1.2.3.tgz create mode 100644 assets/speedscale/speedscale-operator-2.2.314.tgz create mode 100644 charts/external-secrets/external-secrets/0.10.2/Chart.lock create mode 100644 charts/external-secrets/external-secrets/0.10.2/Chart.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/README.md create mode 100644 charts/external-secrets/external-secrets/0.10.2/app-readme.md create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/.helmignore create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/Chart.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/NOTES.txt create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/_helpers.tpl create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/deployment.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/service.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/serviceaccount.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/tests/test-connection.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/values.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/questions.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/NOTES.txt create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/_helpers.tpl create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-deployment.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-poddisruptionbudget.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-rbac.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-service.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-serviceaccount.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/acraccesstoken.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/clusterexternalsecret.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/clustersecretstore.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/ecrauthorizationtoken.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/externalsecret.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/fake.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/gcraccesstoken.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/githubaccesstoken.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/password.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/pushsecret.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/secretstore.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/vaultdynamicsecret.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/crds/webhook.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/deployment.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/extra-manifests.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/poddisruptionbudget.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/rbac.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/service.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/serviceaccount.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/servicemonitor.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/validatingwebhook.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-certificate.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-deployment.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-poddisruptionbudget.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-secret.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-service.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/templates/webhook-serviceaccount.yaml create mode 100644 charts/external-secrets/external-secrets/0.10.2/values.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/CHANGELOG.md create mode 100644 charts/jenkins/jenkins/5.5.12/Chart.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/README.md create mode 100644 charts/jenkins/jenkins/5.5.12/UPGRADING.md create mode 100644 charts/jenkins/jenkins/5.5.12/VALUES.md create mode 100644 charts/jenkins/jenkins/5.5.12/VALUES.md.gotmpl create mode 100644 charts/jenkins/jenkins/5.5.12/templates/NOTES.txt create mode 100644 charts/jenkins/jenkins/5.5.12/templates/_helpers.tpl create mode 100644 charts/jenkins/jenkins/5.5.12/templates/auto-reload-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/config-init-scripts.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/config.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/deprecation.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/home-pvc.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jcasc-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-agent-svc.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-aws-security-group-policies.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-alerting-rules.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-backendconfig.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-ingress.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-networkpolicy.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-pdb.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-podmonitor.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-route.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-secondary-ingress.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-servicemonitor.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-statefulset.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-svc.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/rbac.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/secret-additional.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/secret-claims.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/secret-https-jks.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/secret.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/service-account-agent.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/service-account.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/tests/jenkins-test.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/templates/tests/test-config.yaml create mode 100644 charts/jenkins/jenkins/5.5.12/values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.90.9/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/Chart.lock create mode 100644 charts/jfrog/artifactory-ha/107.90.9/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/LICENSE create mode 100644 charts/jfrog/artifactory-ha/107.90.9/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/app-readme.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.lock create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_affinities.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_errors.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_images.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_ingress.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_labels.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_names.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_storage.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_utils.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_cassandra.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mariadb.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mongodb.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_postgresql.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_redis.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_validations.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/commonAnnotations.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/shmvolume-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/conf.d/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/docker-entrypoint-initdb.d/README.md create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extended-config-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extra-list.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/initialization-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-svc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/prometheusrule.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/role.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/rolebinding.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/servicemonitor.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset-readreplicas.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-headless.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-read.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.schema.json create mode 100644 charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/global-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/large-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/loggers-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/medium-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/migration-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/nginx-autoreload-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/small-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/ci/test-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/binarystore.xml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/installer-info.json create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/migrate.sh create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/migrationHelmInfo.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/migrationStatus.sh create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/nginx-main-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/files/system.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/logo/artifactory-logo.png create mode 100644 charts/jfrog/artifactory-ha/107.90.9/questions.yml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall-extra-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/_system-yaml-render.tpl create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/additional-resources.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/admin-bootstrap-creds.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-access-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-binarystore-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-configmaps.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-custom-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-database-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-gcp-credentials-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-installer-info.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-license-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-migration-scripts.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-nfs-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-node-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-node-statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-primary-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-primary-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-primary-statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-priority-class.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-role.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-rolebinding.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-storage-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-system-yaml.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-unified-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/filebeat-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/ingress.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/logger-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-certificate-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-deployment.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-scripts-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/templates/nginx-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.90.9/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/LICENSE create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/app-readme.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.lock create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/LICENSE create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.lock create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/conf.d/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extra-list.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/role.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/rolebinding.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-headless.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-read.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.schema.json create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/derby-test-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/global-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/large-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/loggers-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/medium-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/migration-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/nginx-autoreload-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/small-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/test-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/binarystore.xml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/installer-info.json create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrate.sh create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrationHelmInfo.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrationStatus.sh create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/nginx-main-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/system.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/logo/artifactory-logo.png create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall-extra-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_system-yaml-render.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/additional-resources.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/admin-bootstrap-creds.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-access-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-binarystore-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-configmaps.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-custom-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-database-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-hpa.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-installer-info.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-license-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-migration-scripts.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-nfs-pvc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-pdb.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-priority-class.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-role.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-rolebinding.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-service.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-statefulset.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-system-yaml.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-unified-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/filebeat-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/ingress.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/logger-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-certificate-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-deployment.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-pdb.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-pvc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-scripts-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-service.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/logo/jcr-logo.png create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/questions.yml create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.90.9/values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/Chart.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/README.md create mode 100644 charts/kubecost/cost-analyzer/2.3.5/app-readme.md create mode 100644 charts/kubecost/cost-analyzer/2.3.5/ci/aggregator-values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/ci/federatedetl-primary-netcosts-values.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/ci/statefulsets-cc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/crds/cluster-turndown-crd.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/custom-pricing.csv create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/README.md create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/attached-disks.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/deployment-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/aggregator-dashboard.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/kubernetes-resource-efficiency.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/label-cost-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/namespace-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/network-cloud-services.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/networkCosts-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/node-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization-multi-cluster.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/prom-benchmark.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics-aggregator.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics.json create mode 100644 charts/kubecost/cost-analyzer/2.3.5/questions.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/scripts/create-admission-controller-tls.sh create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/NOTES.txt create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/_helpers.tpl create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service-account.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-servicemonitor.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/alibaba-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/aws-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-service-account-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/azure-service-key-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/azure-storage-config-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cloud-integration-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-account-mapping-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-advanced-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-alerts-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-asset-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cloud-cost-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-binding-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template-readonly.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-db-pvc-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-frontend-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ingress-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-metrics-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-podmonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-networks-costs-ocp-scc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ocp-route.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-oidc-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pkey-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pricing-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-prometheusrule-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pvc-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saml-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saved-reports-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-server-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-account-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-servicemonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-smtp-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/external-grafana-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/extra-manifests.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/frontend-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/frontend-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/gcpstore-config-map-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrole.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrolebinding.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap-dashboard-provider.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-attached-disks.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-metrics-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-deployment-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-label-cost-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-namespace-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-cloud-sevices.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-costs.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-node-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-prometheus-metrics-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-aggregator.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-metrics.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboards-json-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-datasource-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/grafana-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/install-plugins.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-queries-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-secret.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secretprovider-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-actions-config.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-manager-configmap-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-monitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-oidc-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-priority-class-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-saml-secret-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-configmap-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-deployment-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-service-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/model-ingress-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/network-costs-servicemonitor-template.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/plugins-config.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service-headless.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-daemonset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-ocp-scc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrole.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrolebinding.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-configmap.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-deployment.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-ingress.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-networkpolicy.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pdb.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pvc.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service-headless.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-serviceaccount.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-statefulset.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-vpa.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/tests/_helpers.tpl create mode 100644 charts/kubecost/cost-analyzer/2.3.5/templates/tests/basic-health.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-agent.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-amp.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-cloud-agent.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-custom-pricing.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-eks-cost-monitoring.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-openshift.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values-windows-node-affinity.yaml create mode 100644 charts/kubecost/cost-analyzer/2.3.5/values.yaml create mode 100644 charts/nats/nats/1.2.3/.helmignore create mode 100644 charts/nats/nats/1.2.3/Chart.yaml create mode 100644 charts/nats/nats/1.2.3/README.md create mode 100644 charts/nats/nats/1.2.3/UPGRADING.md create mode 100644 charts/nats/nats/1.2.3/app-readme.md create mode 100644 charts/nats/nats/1.2.3/files/config-map.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/cluster.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/config.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/gateway.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/jetstream.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/leafnodes.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/mqtt.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/protocol.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/resolver.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/tls.yaml create mode 100644 charts/nats/nats/1.2.3/files/config/websocket.yaml create mode 100644 charts/nats/nats/1.2.3/files/headless-service.yaml create mode 100644 charts/nats/nats/1.2.3/files/ingress.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/contents-secret.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/contexts-secret/context.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/contexts-secret/contexts-secret.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/deployment/container.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/deployment/deployment.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/deployment/pod-template.yaml create mode 100644 charts/nats/nats/1.2.3/files/nats-box/service-account.yaml create mode 100644 charts/nats/nats/1.2.3/files/pod-disruption-budget.yaml create mode 100644 charts/nats/nats/1.2.3/files/pod-monitor.yaml create mode 100644 charts/nats/nats/1.2.3/files/service-account.yaml create mode 100644 charts/nats/nats/1.2.3/files/service.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/jetstream-pvc.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/nats-container.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/pod-template.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/prom-exporter-container.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/reloader-container.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/resolver-pvc.yaml create mode 100644 charts/nats/nats/1.2.3/files/stateful-set/stateful-set.yaml create mode 100644 charts/nats/nats/1.2.3/questions.yaml create mode 100644 charts/nats/nats/1.2.3/templates/_helpers.tpl create mode 100644 charts/nats/nats/1.2.3/templates/_jsonpatch.tpl create mode 100644 charts/nats/nats/1.2.3/templates/_toPrettyRawJson.tpl create mode 100644 charts/nats/nats/1.2.3/templates/_tplYaml.tpl create mode 100644 charts/nats/nats/1.2.3/templates/config-map.yaml create mode 100644 charts/nats/nats/1.2.3/templates/extra-resources.yaml create mode 100644 charts/nats/nats/1.2.3/templates/headless-service.yaml create mode 100644 charts/nats/nats/1.2.3/templates/ingress.yaml create mode 100644 charts/nats/nats/1.2.3/templates/nats-box/contents-secret.yaml create mode 100644 charts/nats/nats/1.2.3/templates/nats-box/contexts-secret.yaml create mode 100644 charts/nats/nats/1.2.3/templates/nats-box/deployment.yaml create mode 100644 charts/nats/nats/1.2.3/templates/nats-box/service-account.yaml create mode 100644 charts/nats/nats/1.2.3/templates/pod-disruption-budget.yaml create mode 100644 charts/nats/nats/1.2.3/templates/pod-monitor.yaml create mode 100644 charts/nats/nats/1.2.3/templates/service-account.yaml create mode 100644 charts/nats/nats/1.2.3/templates/service.yaml create mode 100644 charts/nats/nats/1.2.3/templates/stateful-set.yaml create mode 100644 charts/nats/nats/1.2.3/templates/tests/request-reply.yaml create mode 100644 charts/nats/nats/1.2.3/values.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/.helmignore create mode 100644 charts/speedscale/speedscale-operator/2.2.314/Chart.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/LICENSE create mode 100644 charts/speedscale/speedscale-operator/2.2.314/README.md create mode 100644 charts/speedscale/speedscale-operator/2.2.314/app-readme.md create mode 100644 charts/speedscale/speedscale-operator/2.2.314/questions.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/NOTES.txt create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/admission.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/configmap.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/crds/trafficreplays.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/deployments.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/hooks.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/rbac.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/secrets.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/services.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/templates/tls.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.314/values.yaml diff --git a/assets/external-secrets/external-secrets-0.10.2.tgz b/assets/external-secrets/external-secrets-0.10.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0ab28a0a3d346c7473898346671a722836ad42d1 GIT binary patch literal 84898 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 V@cf%T{~rJV|Nj;C|Up literal 0 HcmV?d00001 diff --git a/assets/jenkins/jenkins-5.5.12.tgz b/assets/jenkins/jenkins-5.5.12.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f17bcdf5b0473499a560ceddd37a3471a516e0c4 GIT binary patch literal 77823 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

=(@u_xxR< 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

nbh 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⁢wXQDYc_fKuP*0 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�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̀a7Tv9dx=qSRaj)@T5eMnNP__&D&HAXMXATkzz#VwMPUJqWr3(xs6Ufxr?B ze(+-!@|Z;ITFr;Lf()pkH2+P#Nsu9pXrCosqwyj)ow*Kxcf0`&c&d zL4zrwNg|dqK~A$v)y_sy8Vp$;#5m!;7x0ZHS$*;A=~>ewM*t;}f0;Y6M=2kILUZx* zoOGGwvq6`pOsq?~@54=c5roZ4(v*M7@lN0GN6f{gG9c#Z%AV9E0mOQ5_4Yz zvLSy*X;1~2fuzfaF^^c38hA1T(Pf@a>L5XslyGu)kicR(q%rLT;hf8u~l`yX_KM^r`B=p6ITHV^8vTwUuAyhpOgX^2L-UlEqs*SVRCVE^Jprmn}@Hh#;ifc+0c2#fD8D9WO zk1`z~9#xg# z5*Yn)lJI097Juj5eOb@K(%O8jF0jws`4+RR=WYPC7Xkx1^G0z$p+3u;gYmpX^5&D2 zTnAxDSR_W^0$u6h-4DhrLPOy-gE!bDs5Pi{dE}%=F0&IFB=JkV?cvEspn6LO3BM*Z zA^;$0M(|!pJq?y2LQE(q8(t<+6+0-NvFi`k3A=gKZj!tJrdH$0koIQ_T=%8D-1wQb zM(#`Lx$$FDE?f;S@PC&EmDau@cNK=D(;I~$fs$ZvDbIkS9dq6Ml-uZuqoW(2T&@$r z82NCbtNp&Z0%a1;)28wE6KOJnbW|Rx7(hG>_0XX=Vr(uQ<@%VTNJ)OEI`)$-l!VcC-- zekCP$X!nXmU|je`xi1i^DPK_;QH2PLB;}*+Rc9KPl6F{lpL@(b?gsYEhcHg53`RcMP%OS}snEIr z!3x!*nl9z5uHeiO(R&*XTPLbRfob!0D@K@~Ih?v_Gj}V#Fl+AcOhW#9nE@v^Sc%NY za_BK`<0HYyrQSwli8o`GB!SN^XU<;!07RBQOc?S9ZAg(HC*;o&4JE#PsY(JEWu0-p z&H!vhkysW66^EnDq%+r?)e?)^>8CIATkf3S;f}fBGo5waa%=n!ch&_ToAA+RD!-`B zPF!5p%E4;-+~^&+6eC(8aTa2n9JR0|OFbX;AY{ZDPRvhRH3P}}I9LDo)fL^KP;rzM zGWas6uY|rUHn}84^eIoeD>j+G*IS{_Q}Ehy*_8YYbT6Vip9!S`(WFXuDts(dqf3=u~ii2LvI=TRETy6?7Aez)l~i4(G@ zpAH<-ZV^YM36J_&@VVpxQVOm0|EinZ?~I7o3D#dHSQnI4EPF6iUbkpp;l?euiuzzi z#bwTBsdLszX_}3}Tgf;GYt(A=VSSIxL|^sc)c5r0eK_R_?X%?_Nq?J7cXJ(v#-Ceu2^Mj>^VS97cnNMT9#b*fF-ykiCzy_$g*iN zjYE?>Bl1~-^eBG`6A1$`3z9L?AQG8Bm2Hq7)sP~3e&=@t6V746NQfBlQRou{0Yh_j zH|s{h=q2pW5sT7rqAPcE3drS?OSywe%|ub{+ccPMaJbu(rKJ)29LGO*NODVS+NQmV zfC}8>^MtszK=!9SC#SSw!* zl_#pK(hWgy29Z1@5szG5+8#z(z`kOWc7AXmR#y#Aajkb*h}Xg%P~-uelRx0S{l1@t zeZ!W@Yj5~`>;50mV>6Z+k(ajb&m6JDcS&?PxooZ+0A6&Ph~rDOo2{!TTB+lk$iTdA ze-e={6<~LpWNsDfmRB}7`W=%`v4XOqmBzkE3R zW!3!YBK0{S$Vo~PwYN|r22={L=+prhWr44UiI-kv{+5$O;c`AyJ;-O){1qbUg*w@Q zXb78}>RfwZhV)fo`8n5RW~KM(Pg%FR8YDw3kc0;peuD4I_*h#z#JE( z>fl%q9;rb32_GQ>MuO=}{FT_aM-}%J+8eu=C4bI zppl=@+#D`Ce}}r2C2`R5{24gE@f;dB?rR=r?SJYf$y@G!c!%l#m9YMQ+~p>y?w$U| zn;=4QzndVa_3vpC1yO&tPCtkKek&DwrcyeG0(d(o9E+;26;`~2hhY%)FKAy%018=z zcK9e&k+`Y^NogOLym3msT--^uv9dzWW7Z95NCLp1=nmMB3d&E^cKsLg(5*kdSw2%KQc)>=PZvA=@K9)baw}XjsA9GrBr6 zC4rDL_g}kZ7394U^@HeBqmk17tY)#Dl=kgr+FpCtYr8({1T=EnEjQWSYBb_e7@qPl z=+1-{klmXQip`&ziYDhK7)PHTgx7Q;o|2%K!69+>6p}EePuDSfreG-)Kk2>Z>1o0Q zi_)hEk8(iD0HR4kCk;Y^lwk_2>5%FSPT_Kw828 zf~+HLfGYtL3xg{rzeplxpVEYqAnNgChz^>VH9**Nfw@T~2_HnD6Vs^6RAU{s*H|&s zXA{;+E-gTwELJ3%YbJSxShGxQgf~_Q4qZW{g7a61b;Ou0AeDD^CZ2S%RxD4f14_?E zg2ZE^A@G#@Fr-4vBEeq?Xme$EhPm`GAI_t#l<~8Iqa2ypR z?gv7`DTqIj2<>790ugY@zj6>euG47HZkGx1iu-I9xCc;@47s1(c~T2^uA?(X{r5?b zvNtdRBKY^r&d!5>vjuv1{!7qk#55huYWGxSlIvs>>E~K9Nhqd~vnCy!^p)InAYNC9 zh1dcDRtR7KfL?*a39DN}vw0_?jBFu&#f(wfnclSTk#;4s2Rodo*00#)6^-K{0=AKB zOT9NqLa*$VeItnUD=!(+C!2U)D~! zY(2rF;Ft>$K)~Y!y9G&Mz=A{+u_0Nup4}CaCHPgig7>cLR_8cTSh;zE6QbO`aU-H} zJbzW3!Z$JfbHq%dlx#LWf3}X`m3;q&bauB{)HTV~zkY4x2JOdTHe%27R4O|bjrYv` zsXFP*>B0H=`!{DtjZBst>$AeWhAmU-{lpX zOzrW?%6>R+hx*J?S;?VSiND5*of;F+T3Au}#XoB`k(}LDEwI;bE{@N=^b_)_E{_Bc z*=>>#w_5j`hPc(fK_cQgGZD|cY~t1_iD7O|dXgjcc+!q-sLY~}j%6ewvLIsGWhUk!qXyz~@`vD&1aIH@ z>`9*IS#y_wxu$}ELLzmdUJwOh!2B#^tRJL< zQ3q03NkUec##Tg1(~)p_&rJu+RT%`E>m(fnf(+>&JhAf&z;zN#$YCpS#u%%*0~+<2 zz$8(atxJB-a$%_z2kJt~C5zxm%#DA4mbj_MXgW${Q@)Q$op7jIC{IBva#bn;7`#GE zS84<#NCgXfQpKW)ucQovvg0=|y+&hg?LD~6DZsB;!bqSpmNAuOxQK-Rxwh75xa3s3 zGOO1D(V??@;dmkDC@-E|l2ye(01cf9>F{)bl{R5`TN?LPLvm&@y&DWa-{xkyP0YAO zcoa^W9{KPOmayrm@pMW&Ndhzu)S%E8sR=r8mqwDNd@|&KrA+fh+mFQVrJZ5}oxU0_ zc`LDiGLg#Lbyd`+pR#a&hfO8d<&wxgDc#$V2^j(ip7uJ&Z8Xy+-{x!(3 znWbXmiE?fX;{olkH0Z*D0&#c1hrG`sCTISdPIA^&ov_VH{w7K6P!g)o65RYSGeMS; z02WDL$Owt+M426w6s{i$)U?2r*(} z{w`5oSF1?TspztZCIL5TgK(mhd#GL8=S;}%HN(=Ykwz0aj~N)*C@?rOzt-%b;hK?P zi)nD@k)igJg!NdW+0+N#>jhwEgknDLj--|Yvo?M0j5j}yw>}PoK2Gt*iuvh|1arGo zFws=2M16|-IC**uDJSDCNi1&#>y1JZ2GNy3TF^bq9&=eC(^1NkfQDitVS<1w5dg^B%$5KE literal 0 HcmV?d00001 diff --git a/assets/jfrog/artifactory-jcr-107.90.9.tgz b/assets/jfrog/artifactory-jcr-107.90.9.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7674e2cc4d83fa850baf0b691548ef0937ce3ff4 GIT binary patch literal 459462 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%0D*PY2i?ak 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

`>19kd_)_!l^%g2qmqrYw+|(!vFp$Yi?5!9o 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 Lym*NKphoy#hk7Pf literal 0 HcmV?d00001 diff --git a/assets/kubecost/cost-analyzer-2.3.4.tgz b/assets/kubecost/cost-analyzer-2.3.4.tgz index 53e51888fbd19600fecc1e3103e18ec1208e1623..41a55232be21bd509e6e9c86a5e2c8e1a7bda9f3 100644 GIT binary patch delta 141357 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

O8RUT{X$kX0)?QNTezRuD82jUe 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"PxIYF%wr&+J$ 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

FB4QdO!Z?%^Ikt`gL>)DmHZ8 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 diff --git a/assets/kubecost/cost-analyzer-2.3.5.tgz b/assets/kubecost/cost-analyzer-2.3.5.tgz new file mode 100644 index 0000000000000000000000000000000000000000..74dba8a7d6a95f74a13cd06191d1987fb4cb30da GIT binary patch literal 146470 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%0D+XKWux3o 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 Sj+*y`dB21vP_jsM z_>$ASZf-%sC$1y=kg9Z*rM<^~ky$i{45bJPb_T01lI_WI9w)>7ZjdM5C`MhN1*$5Y_9v z?0{&OJ0RS0k)K#Nq@=$aN=~mzAb$$1Wp{FoxJ}EZbw3wWO0`B-P1q{$y3G5BbuqN4 zG(tGjT>4KSM=kQe)8ux2u&4Fw&*jUCZPP_ie$7nT*0~na>XUHEAZtg~j)v`O1y}d; zUxO?$Cm4tR3{Q%uK9Md<$_TDlHkJuCC(QOEj^=H2&tN>m&Bgu8>cKUdU`imT$!I~2 z*>-H_s*jqg>7y2Z`ZMB3mlfoi(j&t`@L{)5yRvJ8x^HD!9cB>Gg5DAxbC7Dep;=Um z@lH5f!OtwC=^Y%R)JVfazh4!Vz*ijV*^=RLz%D$#D(NhaWo+YEkpy===5NfRMi}0i zz5;LU>Brhp3VC(l`ts02z*UgQV?xb>zji?Z6BkuO?&Y<{Md@T)cd&W+)pC0FMb&hX z7$fM~6b|I)vLi}8OcVba*TqqVnRa22(LM5!iaZOhuns)5x#m{v@y-mL80V{*%D1&x z!r6!^3O~A4IOWUTjTpr8mr4P)fAtXlViT6Hmg|v`Pc#wzhlj(*NLBSv{h>+Pivatk z{u3IS!m)9Y|BKceF52NUt<=%ven@Z6c^+Y;aOdss$bE$9&9gm?TPs5^MGzljb)$~HoCodI zu6c+6uFSrI|HpXuAZ4}~#v&!wKf2d)UjW~*pnVr<8l@quq{^d(dI2oH1oGzGixU$A zgS_Rbu|s{sJO&5vP_@#Oj^{p|lA<<0=RAvoC+%%Y!K}vayp4|zcr<{g+@M_ikvD6i zylfGY#ms+L$b_K(8sX6L^UbAiC9)#TWfxiG}Q}MQEwoa2Gb;f8YIy#!&NfrLNv*G%4 z&ZHD5nP1U;@UU-qKQ-Q}89C8VjUx&s`4>>pu<@tV(jZX{4uqt(p6plfxSC2jvJ;9s zckIDMg{DsczxM&_svz+xX>t>BKkqloX`ot-+m<$ppQgg-yo z(?00ik^nX_RfjI2XAsw(-kmZ0Q~HauJSSk9TwZl0wB<0^fC>mG|r$^oT_pW_JkmQOh4=g$+JtA~40L1O2W(7H>YTmS{mBX-=Lg121N3`-*@4I$yF}1B_kP|ssE|Pd{ zZ)GA^&aAE4Lv}5*hr+X(&gNL|G)zm$rUMCZ0?1*qe-zQ7gU~~FBX$2|XQ5lCBYfQh z@`6qrRn|R%JGjZ>%N+2~;tt9dqXa^3?o1*=NEG+=pI`nnQgd@Jqf^ZQjyT4>t z*1fYxwx2I1&5ORu5T@q<_K}<03H6G6$UdV}K*GQJjN8lp82+7sCZynf1}JvRc9C;? z%e72>&k_4lscL=g*{a1BVf?`hTi$XpO3tJ*r#DGj@&CnOl(5L+y_T-zSIXP4B9k3%d(#Lr1 zX5blKd`Fqi*`gHYl!*F)u#R0qbfDB|Swf_skR15x;eFpFisS5V&4L3;A&f^&aj`mJ zqp3*{-glT&JDgPc<;N3gUaLLzT5ZTDtT-L^j=k*8s`yt|2RZzdbI7Qs0-5g(4yA

+g$gi6($T(YaRJ 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 Go)iGoapdR# literal 0 HcmV?d00001 diff --git a/assets/nats/nats-1.2.3.tgz b/assets/nats/nats-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..752f78064be9baf4c4b61bd159b2b98ef72d56d9 GIT binary patch literal 20135 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 WTeju%mj6Eh0RR6q8Hn`&6aoNf88YMm literal 0 HcmV?d00001 diff --git a/assets/speedscale/speedscale-operator-2.2.314.tgz b/assets/speedscale/speedscale-operator-2.2.314.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ce7bf93f05b0b94310762b68caadee2d321801c9 GIT binary patch literal 16745 zcmV)RK(oIeiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PMYed)u~>IC}oQ`V=@y_etCpCEJOUuJ-ioRc)p9*0Fspr#-z* zuM3fogqQ?a0FrP(#la~g{T27|%OU}o?#GAAUFF-}Rub0Vp55Ku-KS5Uz`wh@yY+wf_Mbfc+uoC>Pai*fvj1%V>ECwu4)zY7 z{tfNEpXS#;g;ZGlZTHS?)jRi@JeV=Au%evFehZ-(E1dFaH^xe(q)T}(CPL8(B}pIU zSSUtBW3hzFJjIKM;f(ar$a57svs~3!>$(_|XikKrob}Pee}0Yjx_i&MyV35$#`1!s z1WRIDY2e)!≷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 NKV9TKmmz?W0RYk^=1Bkm literal 0 HcmV?d00001 diff --git a/charts/external-secrets/external-secrets/0.10.2/Chart.lock b/charts/external-secrets/external-secrets/0.10.2/Chart.lock new file mode 100644 index 0000000000..1d198fe7ce --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: bitwarden-sdk-server + repository: oci://ghcr.io/external-secrets/charts + version: v0.1.4 +digest: sha256:f60d5e4c6ad432fc7efdb0dad33774afaa88e02bd82eb9d5224372828f7d52be +generated: "2024-06-20T10:01:52.49841+02:00" diff --git a/charts/external-secrets/external-secrets/0.10.2/Chart.yaml b/charts/external-secrets/external-secrets/0.10.2/Chart.yaml new file mode 100644 index 0000000000..6b11633052 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/Chart.yaml @@ -0,0 +1,25 @@ +annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: External Secrets Operator + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: external-secrets +apiVersion: v2 +appVersion: v0.10.2 +dependencies: +- condition: bitwarden-sdk-server.enabled + name: bitwarden-sdk-server + repository: file://./charts/bitwarden-sdk-server + version: v0.1.4 +description: External secret management for Kubernetes +home: https://github.com/external-secrets/external-secrets +icon: file://assets/icons/external-secrets.png +keywords: +- kubernetes-external-secrets +- secrets +kubeVersion: '>= 1.19.0-0' +maintainers: +- email: kellinmcavoy@gmail.com + name: mcavoyk +name: external-secrets +type: application +version: 0.10.2 diff --git a/charts/external-secrets/external-secrets/0.10.2/README.md b/charts/external-secrets/external-secrets/0.10.2/README.md new file mode 100644 index 0000000000..a1cd2a873a --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/README.md @@ -0,0 +1,225 @@ +# External Secrets + +

external-secrets

+ +[//]: # (README.md generated by gotmpl. DO NOT EDIT.) + +![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![Version: 0.10.2](https://img.shields.io/badge/Version-0.10.2-informational?style=flat-square) + +External secret management for Kubernetes + +## TL;DR +```bash +helm repo add external-secrets https://charts.external-secrets.io +helm install external-secrets external-secrets/external-secrets +``` + +## Installing the Chart +To install the chart with the release name `external-secrets`: +```bash +helm install external-secrets external-secrets/external-secrets +``` + +### Custom Resources +By default, the chart will install external-secrets CRDs, this can be controlled with `installCRDs` value. + +## Uninstalling the Chart +To uninstall the `external-secrets` deployment: +```bash +helm uninstall external-secrets +``` +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| bitwarden-sdk-server.enabled | bool | `false` | | +| certController.affinity | object | `{}` | | +| certController.create | bool | `true` | Specifies whether a certificate controller deployment be created. | +| certController.deploymentAnnotations | object | `{}` | Annotations to add to Deployment | +| certController.extraArgs | object | `{}` | | +| certController.extraEnv | list | `[]` | | +| certController.extraVolumeMounts | list | `[]` | | +| certController.extraVolumes | list | `[]` | | +| certController.fullnameOverride | string | `""` | | +| certController.hostNetwork | bool | `false` | Run the certController on the host network | +| certController.image.flavour | string | `""` | | +| certController.image.pullPolicy | string | `"IfNotPresent"` | | +| certController.image.repository | string | `"ghcr.io/external-secrets/external-secrets"` | | +| certController.image.tag | string | `""` | | +| certController.imagePullSecrets | list | `[]` | | +| certController.log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifices Log Params to the Webhook | +| certController.metrics.listen.port | int | `8080` | | +| certController.metrics.service.annotations | object | `{}` | Additional service annotations | +| certController.metrics.service.enabled | bool | `false` | Enable if you use another monitoring tool than Prometheus to scrape the metrics | +| certController.metrics.service.port | int | `8080` | Metrics service port to scrape | +| certController.nameOverride | string | `""` | | +| certController.nodeSelector | object | `{}` | | +| certController.podAnnotations | object | `{}` | Annotations to add to Pod | +| certController.podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ | +| certController.podLabels | object | `{}` | | +| certController.podSecurityContext.enabled | bool | `true` | | +| certController.priorityClassName | string | `""` | Pod priority class name. | +| certController.rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. | +| certController.readinessProbe.address | string | `""` | Address for readiness probe | +| certController.readinessProbe.port | int | `8081` | ReadinessProbe port for kubelet | +| certController.replicaCount | int | `1` | | +| certController.requeueInterval | string | `"5m"` | | +| certController.resources | object | `{}` | | +| certController.revisionHistoryLimit | int | `10` | Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) | +| certController.securityContext.allowPrivilegeEscalation | bool | `false` | | +| certController.securityContext.capabilities.drop[0] | string | `"ALL"` | | +| certController.securityContext.enabled | bool | `true` | | +| certController.securityContext.readOnlyRootFilesystem | bool | `true` | | +| certController.securityContext.runAsNonRoot | bool | `true` | | +| certController.securityContext.runAsUser | int | `1000` | | +| certController.securityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| certController.serviceAccount.annotations | object | `{}` | Annotations to add to the service account. | +| certController.serviceAccount.automount | bool | `true` | Automounts the service account token in all containers of the pod | +| certController.serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| certController.serviceAccount.extraLabels | object | `{}` | Extra Labels to add to the service account. | +| certController.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | +| certController.tolerations | list | `[]` | | +| certController.topologySpreadConstraints | list | `[]` | | +| commonLabels | object | `{}` | Additional labels added to all helm chart resources. | +| concurrent | int | `1` | Specifies the number of concurrent ExternalSecret Reconciles external-secret executes at a time. | +| controllerClass | string | `""` | If set external secrets will filter matching Secret Stores with the appropriate controller values. | +| crds.annotations | object | `{}` | | +| crds.conversion.enabled | bool | `true` | | +| crds.createClusterExternalSecret | bool | `true` | If true, create CRDs for Cluster External Secret. | +| crds.createClusterSecretStore | bool | `true` | If true, create CRDs for Cluster Secret Store. | +| crds.createPushSecret | bool | `true` | If true, create CRDs for Push Secret. | +| createOperator | bool | `true` | Specifies whether an external secret operator deployment be created. | +| deploymentAnnotations | object | `{}` | Annotations to add to Deployment | +| dnsConfig | object | `{}` | Specifies `dnsOptions` to deployment | +| dnsPolicy | string | `"ClusterFirst"` | Specifies `dnsPolicy` to deployment | +| extendedMetricLabels | bool | `false` | If true external secrets will use recommended kubernetes annotations as prometheus metric labels. | +| extraArgs | object | `{}` | | +| extraContainers | list | `[]` | | +| extraEnv | list | `[]` | | +| extraObjects | list | `[]` | | +| extraVolumeMounts | list | `[]` | | +| extraVolumes | list | `[]` | | +| fullnameOverride | string | `""` | | +| global.affinity | object | `{}` | | +| global.compatibility.openshift.adaptSecurityContext | string | `"auto"` | Manages the securityContext properties to make them compatible with OpenShift. Possible values: auto - Apply configurations if it is detected that OpenShift is the target platform. force - Always apply configurations. disabled - No modification applied. | +| global.nodeSelector | object | `{}` | | +| global.tolerations | list | `[]` | | +| global.topologySpreadConstraints | list | `[]` | | +| hostNetwork | bool | `false` | Run the controller on the host network | +| image.flavour | string | `""` | The flavour of tag you want to use There are different image flavours available, like distroless and ubi. Please see GitHub release notes for image tags for these flavors. By default, the distroless image is used. | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"ghcr.io/external-secrets/external-secrets"` | | +| image.tag | string | `""` | The image tag to use. The default is the chart appVersion. | +| imagePullSecrets | list | `[]` | | +| installCRDs | bool | `true` | If set, install and upgrade CRDs through helm chart. | +| leaderElect | bool | `false` | If true, external-secrets will perform leader election between instances to ensure no more than one instance of external-secrets operates at a time. | +| log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifices Log Params to the Webhook | +| metrics.listen.port | int | `8080` | | +| metrics.service.annotations | object | `{}` | Additional service annotations | +| metrics.service.enabled | bool | `false` | Enable if you use another monitoring tool than Prometheus to scrape the metrics | +| metrics.service.port | int | `8080` | Metrics service port to scrape | +| nameOverride | string | `""` | | +| namespaceOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | Annotations to add to Pod | +| podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ | +| podLabels | object | `{}` | | +| podSecurityContext.enabled | bool | `true` | | +| podSpecExtra | object | `{}` | Any extra pod spec on the deployment | +| priorityClassName | string | `""` | Pod priority class name. | +| processClusterExternalSecret | bool | `true` | if true, the operator will process cluster external secret. Else, it will ignore them. | +| processClusterStore | bool | `true` | if true, the operator will process cluster store. Else, it will ignore them. | +| processPushSecret | bool | `true` | if true, the operator will process push secret. Else, it will ignore them. | +| rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. | +| rbac.servicebindings.create | bool | `true` | Specifies whether a clusterrole to give servicebindings read access should be created. | +| replicaCount | int | `1` | | +| resources | object | `{}` | | +| revisionHistoryLimit | int | `10` | Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) | +| scopedNamespace | string | `""` | If set external secrets are only reconciled in the provided namespace | +| scopedRBAC | bool | `false` | Must be used with scopedNamespace. If true, create scoped RBAC roles under the scoped namespace and implicitly disable cluster stores and cluster external secrets | +| securityContext.allowPrivilegeEscalation | bool | `false` | | +| securityContext.capabilities.drop[0] | string | `"ALL"` | | +| securityContext.enabled | bool | `true` | | +| securityContext.readOnlyRootFilesystem | bool | `true` | | +| securityContext.runAsNonRoot | bool | `true` | | +| securityContext.runAsUser | int | `1000` | | +| securityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| service.ipFamilies | list | `[]` | Sets the families that should be supported and the order in which they should be applied to ClusterIP as well. Can be IPv4 and/or IPv6. | +| service.ipFamilyPolicy | string | `""` | Set the ip family policy to configure dual-stack see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services) | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account. | +| serviceAccount.automount | bool | `true` | Automounts the service account token in all containers of the pod | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| serviceAccount.extraLabels | object | `{}` | Extra Labels to add to the service account. | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | +| serviceMonitor.additionalLabels | object | `{}` | Additional labels | +| serviceMonitor.enabled | bool | `false` | Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics | +| serviceMonitor.honorLabels | bool | `false` | Let prometheus add an exported_ prefix to conflicting labels | +| serviceMonitor.interval | string | `"30s"` | Interval to scrape metrics | +| serviceMonitor.metricRelabelings | list | `[]` | Metric relabel configs to apply to samples before ingestion. [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs) | +| serviceMonitor.namespace | string | `""` | namespace where you want to install ServiceMonitors | +| serviceMonitor.relabelings | list | `[]` | Relabel configs to apply to samples before ingestion. [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) | +| serviceMonitor.scrapeTimeout | string | `"25s"` | Timeout if metrics can't be retrieved in given time interval | +| tolerations | list | `[]` | | +| topologySpreadConstraints | list | `[]` | | +| webhook.affinity | object | `{}` | | +| webhook.certCheckInterval | string | `"5m"` | Specifices the time to check if the cert is valid | +| webhook.certDir | string | `"/tmp/certs"` | | +| webhook.certManager.addInjectorAnnotations | bool | `true` | Automatically add the cert-manager.io/inject-ca-from annotation to the webhooks and CRDs. As long as you have the cert-manager CA Injector enabled, this will automatically setup your webhook's CA to the one used by cert-manager. See https://cert-manager.io/docs/concepts/ca-injector | +| webhook.certManager.cert.annotations | object | `{}` | Add extra annotations to the Certificate resource. | +| webhook.certManager.cert.create | bool | `true` | Create a certificate resource within this chart. See https://cert-manager.io/docs/usage/certificate/ | +| webhook.certManager.cert.duration | string | `"8760h"` | Set the requested duration (i.e. lifetime) of the Certificate. See https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec One year by default. | +| webhook.certManager.cert.issuerRef | object | `{"group":"cert-manager.io","kind":"Issuer","name":"my-issuer"}` | For the Certificate created by this chart, setup the issuer. See https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.IssuerSpec | +| webhook.certManager.cert.renewBefore | string | `""` | How long before the currently issued certificate’s expiry cert-manager should renew the certificate. See https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec Note that renewBefore should be greater than .webhook.lookaheadInterval since the webhook will check this far in advance that the certificate is valid. | +| webhook.certManager.enabled | bool | `false` | Enabling cert-manager support will disable the built in secret and switch to using cert-manager (installed separately) to automatically issue and renew the webhook certificate. This chart does not install cert-manager for you, See https://cert-manager.io/docs/ | +| webhook.create | bool | `true` | Specifies whether a webhook deployment be created. | +| webhook.deploymentAnnotations | object | `{}` | Annotations to add to Deployment | +| webhook.extraArgs | object | `{}` | | +| webhook.extraEnv | list | `[]` | | +| webhook.extraVolumeMounts | list | `[]` | | +| webhook.extraVolumes | list | `[]` | | +| webhook.failurePolicy | string | `"Fail"` | Specifies whether validating webhooks should be created with failurePolicy: Fail or Ignore | +| webhook.fullnameOverride | string | `""` | | +| webhook.hostNetwork | bool | `false` | Specifies if webhook pod should use hostNetwork or not. | +| webhook.image.flavour | string | `""` | The flavour of tag you want to use | +| webhook.image.pullPolicy | string | `"IfNotPresent"` | | +| webhook.image.repository | string | `"ghcr.io/external-secrets/external-secrets"` | | +| webhook.image.tag | string | `""` | The image tag to use. The default is the chart appVersion. | +| webhook.imagePullSecrets | list | `[]` | | +| webhook.log | object | `{"level":"info","timeEncoding":"epoch"}` | Specifices Log Params to the Webhook | +| webhook.lookaheadInterval | string | `""` | Specifices the lookaheadInterval for certificate validity | +| webhook.metrics.listen.port | int | `8080` | | +| webhook.metrics.service.annotations | object | `{}` | Additional service annotations | +| webhook.metrics.service.enabled | bool | `false` | Enable if you use another monitoring tool than Prometheus to scrape the metrics | +| webhook.metrics.service.port | int | `8080` | Metrics service port to scrape | +| webhook.nameOverride | string | `""` | | +| webhook.nodeSelector | object | `{}` | | +| webhook.podAnnotations | object | `{}` | Annotations to add to Pod | +| webhook.podDisruptionBudget | object | `{"enabled":false,"minAvailable":1}` | Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ | +| webhook.podLabels | object | `{}` | | +| webhook.podSecurityContext.enabled | bool | `true` | | +| webhook.port | int | `10250` | The port the webhook will listen to | +| webhook.priorityClassName | string | `""` | Pod priority class name. | +| webhook.rbac.create | bool | `true` | Specifies whether role and rolebinding resources should be created. | +| webhook.readinessProbe.address | string | `""` | Address for readiness probe | +| webhook.readinessProbe.port | int | `8081` | ReadinessProbe port for kubelet | +| webhook.replicaCount | int | `1` | | +| webhook.resources | object | `{}` | | +| webhook.revisionHistoryLimit | int | `10` | Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) | +| webhook.secretAnnotations | object | `{}` | Annotations to add to Secret | +| webhook.securityContext.allowPrivilegeEscalation | bool | `false` | | +| webhook.securityContext.capabilities.drop[0] | string | `"ALL"` | | +| webhook.securityContext.enabled | bool | `true` | | +| webhook.securityContext.readOnlyRootFilesystem | bool | `true` | | +| webhook.securityContext.runAsNonRoot | bool | `true` | | +| webhook.securityContext.runAsUser | int | `1000` | | +| webhook.securityContext.seccompProfile.type | string | `"RuntimeDefault"` | | +| webhook.serviceAccount.annotations | object | `{}` | Annotations to add to the service account. | +| webhook.serviceAccount.automount | bool | `true` | Automounts the service account token in all containers of the pod | +| webhook.serviceAccount.create | bool | `true` | Specifies whether a service account should be created. | +| webhook.serviceAccount.extraLabels | object | `{}` | Extra Labels to add to the service account. | +| webhook.serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | +| webhook.tolerations | list | `[]` | | +| webhook.topologySpreadConstraints | list | `[]` | | diff --git a/charts/external-secrets/external-secrets/0.10.2/app-readme.md b/charts/external-secrets/external-secrets/0.10.2/app-readme.md new file mode 100644 index 0000000000..2a28bc3992 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/app-readme.md @@ -0,0 +1,7 @@ +**External Secrets Operator** is a Kubernetes operator that integrates external secret management systems like [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/), [HashiCorp Vault](https://www.vaultproject.io/), [Google Secrets Manager](https://cloud.google.com/secret-manager), [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) and many more. +The operator reads information from external APIs and automatically injects the values into a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/). + +### What is the goal of External Secrets Operator? + +The goal of External Secrets Operator is to synchronize secrets from external APIs into Kubernetes. ESO is a collection of custom API resources - `ExternalSecret`, `SecretStore` and `ClusterSecretStore` that provide a user-friendly abstraction for the external API that stores and manages the lifecycle of the secrets for you. + diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/.helmignore b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/Chart.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/Chart.yaml new file mode 100644 index 0000000000..00fdedd90b --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: v0.1.4 +description: A Helm chart for Kubernetes +name: bitwarden-sdk-server +type: application +version: v0.1.4 diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/NOTES.txt b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/NOTES.txt new file mode 100644 index 0000000000..46b671c6a3 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "bitwarden-sdk-server.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "bitwarden-sdk-server.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "bitwarden-sdk-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "bitwarden-sdk-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/_helpers.tpl b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/_helpers.tpl new file mode 100644 index 0000000000..a5e0da3cca --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "bitwarden-sdk-server.name" -}} +{{- default .Chart.Name .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 "bitwarden-sdk-server.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "bitwarden-sdk-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "bitwarden-sdk-server.labels" -}} +helm.sh/chart: {{ include "bitwarden-sdk-server.chart" . }} +{{ include "bitwarden-sdk-server.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "bitwarden-sdk-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "bitwarden-sdk-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "bitwarden-sdk-server.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "bitwarden-sdk-server.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/deployment.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/deployment.yaml new file mode 100644 index 0000000000..a24790659f --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "bitwarden-sdk-server.fullname" . }} + labels: + {{- include "bitwarden-sdk-server.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "bitwarden-sdk-server.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "bitwarden-sdk-server.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "bitwarden-sdk-server.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + {{- if not .Values.image.tls.enabled }} + args: + - --insecure + {{- end }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.image.tls.enabled }} + volumeMounts: + {{- toYaml .Values.image.tls.volumeMounts | nindent 10 }} + {{- end}} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if not .Values.image.tls.enabled }} + livenessProbe: + httpGet: + path: /live + port: http + scheme: HTTPS + readinessProbe: + httpGet: + path: /ready + port: http + scheme: HTTPS + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.image.tls.enabled }} + volumes: + {{- toYaml .Values.image.tls.volumes | nindent 8 }} + {{- end}} diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/service.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/service.yaml new file mode 100644 index 0000000000..88e2d668b5 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "bitwarden-sdk-server.fullname" . }} + labels: + {{- include "bitwarden-sdk-server.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + name: http + selector: + {{- include "bitwarden-sdk-server.selectorLabels" . | nindent 4 }} diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/serviceaccount.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/serviceaccount.yaml new file mode 100644 index 0000000000..fef7bad653 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "bitwarden-sdk-server.serviceAccountName" . }} + labels: + {{- include "bitwarden-sdk-server.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/tests/test-connection.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/tests/test-connection.yaml new file mode 100644 index 0000000000..9f5ca59c48 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "bitwarden-sdk-server.fullname" . }}-test-connection" + labels: + {{- include "bitwarden-sdk-server.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "bitwarden-sdk-server.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/values.yaml b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/values.yaml new file mode 100644 index 0000000000..f0424afbad --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/charts/bitwarden-sdk-server/values.yaml @@ -0,0 +1,98 @@ +# Default values for bitwarden-sdk-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: ghcr.io/external-secrets/bitwarden-sdk-server + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + tls: + enabled: true + volumeMounts: + - mountPath: "/certs" + name: "bitwarden-tls-certs" + volumes: + - name: "bitwarden-tls-certs" + secret: + secretName: "bitwarden-tls-certs" + items: + - key: "tls.crt" + path: "cert.pem" + - key: "tls.key" + path: "key.pem" + - key: "ca.crt" + path: "ca.pem" + +imagePullSecrets: [] +nameOverride: "bitwarden-sdk-server" +fullnameOverride: "bitwarden-sdk-server" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 9998 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/charts/external-secrets/external-secrets/0.10.2/questions.yaml b/charts/external-secrets/external-secrets/0.10.2/questions.yaml new file mode 100644 index 0000000000..31008999d3 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/questions.yaml @@ -0,0 +1,8 @@ +questions: +- variable: installCRDs + default: false + required: true + description: "If true, Install and upgrade CRDs through helm chart" + type: boolean + label: Install CRDs + diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/NOTES.txt b/charts/external-secrets/external-secrets/0.10.2/templates/NOTES.txt new file mode 100644 index 0000000000..ffa0fc7e18 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/NOTES.txt @@ -0,0 +1,7 @@ +external-secrets has been deployed successfully in namespace {{ template "external-secrets.namespace" . }}! + +In order to begin using ExternalSecrets, you will need to set up a SecretStore +or ClusterSecretStore resource (for example, by creating a 'vault' SecretStore). + +More information on the different types of SecretStores and how to configure them +can be found in our Github: {{ .Chart.Home }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/_helpers.tpl b/charts/external-secrets/external-secrets/0.10.2/templates/_helpers.tpl new file mode 100644 index 0000000000..d5eea07593 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/_helpers.tpl @@ -0,0 +1,198 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "external-secrets.name" -}} +{{- default .Chart.Name .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 "external-secrets.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .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 }} + +{{/* +Define namespace of chart, useful for multi-namespace deployments +*/}} +{{- define "external-secrets.namespace" -}} +{{- if .Values.namespaceOverride }} +{{- .Values.namespaceOverride }} +{{- else }} +{{- .Release.Namespace }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "external-secrets.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "external-secrets.labels" -}} +helm.sh/chart: {{ include "external-secrets.chart" . }} +{{ include "external-secrets.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{- define "external-secrets-webhook.labels" -}} +helm.sh/chart: {{ include "external-secrets.chart" . }} +{{ include "external-secrets-webhook.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{- define "external-secrets-webhook-metrics.labels" -}} +{{ include "external-secrets-webhook.selectorLabels" . }} +app.kubernetes.io/metrics: "webhook" +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{- define "external-secrets-cert-controller.labels" -}} +helm.sh/chart: {{ include "external-secrets.chart" . }} +{{ include "external-secrets-cert-controller.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{- define "external-secrets-cert-controller-metrics.labels" -}} +{{ include "external-secrets-cert-controller.selectorLabels" . }} +app.kubernetes.io/metrics: "cert-controller" +{{- with .Values.commonLabels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "external-secrets.selectorLabels" -}} +app.kubernetes.io/name: {{ include "external-secrets.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{- define "external-secrets-webhook.selectorLabels" -}} +app.kubernetes.io/name: {{ include "external-secrets.name" . }}-webhook +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{- define "external-secrets-cert-controller.selectorLabels" -}} +app.kubernetes.io/name: {{ include "external-secrets.name" . }}-cert-controller +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{/* +Create the name of the service account to use +*/}} +{{- define "external-secrets.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "external-secrets.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "external-secrets-webhook.serviceAccountName" -}} +{{- if .Values.webhook.serviceAccount.create }} +{{- default "external-secrets-webhook" .Values.webhook.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.webhook.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "external-secrets-cert-controller.serviceAccountName" -}} +{{- if .Values.certController.serviceAccount.create }} +{{- default "external-secrets-cert-controller" .Values.certController.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.certController.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Determine the image to use, including if using a flavour. +*/}} +{{- define "external-secrets.image" -}} +{{- if .image.flavour -}} +{{ printf "%s:%s-%s" .image.repository (.image.tag | default .chartAppVersion) .image.flavour }} +{{- else }} +{{ printf "%s:%s" .image.repository (.image.tag | default .chartAppVersion) }} +{{- end }} +{{- end }} + +{{/* +Renders a complete tree, even values that contains template. +*/}} +{{- define "external-secrets.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{ else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{/* +Return true if the OpenShift is the detected platform +Usage: +{{- include "external-secrets.isOpenShift" . -}} +*/}} +{{- define "external-secrets.isOpenShift" -}} +{{- if .Capabilities.APIVersions.Has "security.openshift.io/v1" -}} +{{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Render the securityContext based on the provided securityContext + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" .Values.securityContext "context" $) -}} +*/}} +{{- define "external-secrets.renderSecurityContext" -}} +{{- $adaptedContext := .securityContext -}} +{{- if .context.Values.global.compatibility -}} + {{- if .context.Values.global.compatibility.openshift -}} + {{- if or (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "force") (and (eq .context.Values.global.compatibility.openshift.adaptSecurityContext "auto") (include "external-secrets.isOpenShift" .context)) -}} + {{/* Remove OpenShift managed fields */}} + {{- $adaptedContext = omit $adaptedContext "fsGroup" "runAsUser" "runAsGroup" -}} + {{- if not .securityContext.seLinuxOptions -}} + {{- $adaptedContext = omit $adaptedContext "seLinuxOptions" -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- omit $adaptedContext "enabled" | toYaml -}} +{{- end -}} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-deployment.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-deployment.yaml new file mode 100644 index 0000000000..a843f045a0 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-deployment.yaml @@ -0,0 +1,124 @@ +{{- if and .Values.certController.create (not .Values.webhook.certManager.enabled) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} + {{- with .Values.certController.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.certController.replicaCount }} + revisionHistoryLimit: {{ .Values.certController.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.certController.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 8 }} + {{- with .Values.certController.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.certController.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "external-secrets-cert-controller.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.certController.serviceAccount.automount }} + {{- with .Values.certController.podSecurityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 8 }} + {{- end }} + {{- end }} + hostNetwork: {{ .Values.certController.hostNetwork }} + containers: + - name: cert-controller + {{- with .Values.certController.securityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 12 }} + {{- end }} + {{- end }} + image: {{ include "external-secrets.image" (dict "chartAppVersion" .Chart.AppVersion "image" .Values.certController.image) | trim }} + imagePullPolicy: {{ .Values.certController.image.pullPolicy }} + args: + - certcontroller + - --crd-requeue-interval={{ .Values.certController.requeueInterval }} + - --service-name={{ include "external-secrets.fullname" . }}-webhook + - --service-namespace={{ template "external-secrets.namespace" . }} + - --secret-name={{ include "external-secrets.fullname" . }}-webhook + - --secret-namespace={{ template "external-secrets.namespace" . }} + - --metrics-addr=:{{ .Values.certController.metrics.listen.port }} + - --healthz-addr={{ .Values.certController.readinessProbe.address }}:{{ .Values.certController.readinessProbe.port }} + - --loglevel={{ .Values.certController.log.level }} + - --zap-time-encoding={{ .Values.certController.log.timeEncoding }} + {{- if not .Values.crds.createClusterSecretStore }} + - --crd-names=externalsecrets.external-secrets.io + - --crd-names=secretstores.external-secrets.io + {{- end }} + {{- if .Values.installCRDs }} + - --enable-partial-cache=true + {{- end }} + {{- range $key, $value := .Values.certController.extraArgs }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + ports: + - containerPort: {{ .Values.certController.metrics.listen.port }} + protocol: TCP + name: metrics + readinessProbe: + httpGet: + port: {{ .Values.certController.readinessProbe.port }} + path: /readyz + initialDelaySeconds: 20 + periodSeconds: 5 + {{- with .Values.certController.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.certController.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.certController.extraVolumeMounts }} + volumeMounts: + {{- toYaml .Values.certController.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.certController.extraVolumes }} + volumes: + {{- toYaml .Values.certController.extraVolumes | nindent 8 }} + {{- end }} + {{- with .Values.certController.nodeSelector | default .Values.global.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.certController.affinity | default .Values.global.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.certController.tolerations | default .Values.global.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.certController.topologySpreadConstraints | default .Values.global.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.certController.priorityClassName }} + priorityClassName: {{ .Values.certController.priorityClassName }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-poddisruptionbudget.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-poddisruptionbudget.yaml new file mode 100644 index 0000000000..e61cb8ebcf --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-poddisruptionbudget.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.certController.create .Values.certController.podDisruptionBudget.enabled (not .Values.webhook.certManager.enabled) }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller-pdb + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} +spec: + {{- if .Values.certController.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.certController.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.certController.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.certController.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-rbac.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-rbac.yaml new file mode 100644 index 0000000000..84a0c110bd --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-rbac.yaml @@ -0,0 +1,86 @@ +{{- if and .Values.certController.create .Values.certController.rbac.create (not .Values.webhook.certManager.enabled) -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} +rules: + - apiGroups: + - "apiextensions.k8s.io" + resources: + - "customresourcedefinitions" + verbs: + - "get" + - "list" + - "watch" + - "update" + - "patch" + - apiGroups: + - "admissionregistration.k8s.io" + resources: + - "validatingwebhookconfigurations" + verbs: + - "list" + - "watch" + - "get" + - apiGroups: + - "admissionregistration.k8s.io" + resources: + - "validatingwebhookconfigurations" + resourceNames: + - "secretstore-validate" + - "externalsecret-validate" + verbs: + - "update" + - "patch" + - apiGroups: + - "" + resources: + - "endpoints" + verbs: + - "list" + - "get" + - "watch" + - apiGroups: + - "" + resources: + - "events" + verbs: + - "create" + - "patch" + - apiGroups: + - "" + resources: + - "secrets" + verbs: + - "get" + - "list" + - "watch" + - "update" + - "patch" + - apiGroups: + - "coordination.k8s.io" + resources: + - "leases" + verbs: + - "get" + - "create" + - "update" + - "patch" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "external-secrets.fullname" . }}-cert-controller +subjects: + - name: {{ include "external-secrets-cert-controller.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} + kind: ServiceAccount +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-service.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-service.yaml new file mode 100644 index 0000000000..12cb4f4dae --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-service.yaml @@ -0,0 +1,28 @@ +{{- if and .Values.certController.create .Values.certController.metrics.service.enabled (not .Values.webhook.certManager.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller-metrics + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + {{- with .Values.metrics.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: {{ .Values.certController.metrics.service.port }} + protocol: TCP + targetPort: metrics + name: metrics + selector: + {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-serviceaccount.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-serviceaccount.yaml new file mode 100644 index 0000000000..6a36f9d713 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/cert-controller-serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.certController.create .Values.certController.serviceAccount.create (not .Values.webhook.certManager.enabled) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "external-secrets-cert-controller.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} + {{- with .Values.certController.serviceAccount.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.certController.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/acraccesstoken.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/acraccesstoken.yaml new file mode 100644 index 0000000000..7dc3c8e7d7 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/acraccesstoken.yaml @@ -0,0 +1,203 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: acraccesstokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - acraccesstoken + kind: ACRAccessToken + listKind: ACRAccessTokenList + plural: acraccesstokens + shortNames: + - acraccesstoken + singular: acraccesstoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ACRAccessToken returns a Azure Container Registry token + that can be used for pushing/pulling images. + Note: by default it will return an ACR Refresh Token with full access + (depending on the identity). + This can be scoped down to the repository level using .spec.scope. + In case scope is defined it will return an ACR Access Token. + + See docs: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md + 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: |- + ACRAccessTokenSpec defines how to generate the access token + e.g. how to authenticate and which registry to use. + see: https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#overview + properties: + auth: + properties: + managedIdentity: + description: ManagedIdentity uses Azure Managed Identity to authenticate with Azure. + properties: + identityId: + description: If multiple Managed Identity is assigned to the pod, you can select the one to be used + type: string + type: object + servicePrincipal: + description: ServicePrincipal uses Azure Service Principal credentials to authenticate with Azure. + properties: + secretRef: + description: |- + Configuration used to authenticate with Azure using static + credentials stored in a Kind=Secret. + properties: + clientId: + description: The Azure clientId of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: The Azure ClientSecret of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + workloadIdentity: + description: WorkloadIdentity uses Azure Workload Identity to authenticate with Azure. + properties: + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + type: object + environmentType: + default: PublicCloud + description: |- + EnvironmentType specifies the Azure cloud environment endpoints to use for + connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. + The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 + PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud + enum: + - PublicCloud + - USGovernmentCloud + - ChinaCloud + - GermanCloud + type: string + registry: + description: |- + the domain name of the ACR registry + e.g. foobarexample.azurecr.io + type: string + scope: + description: |- + Define the scope for the access token, e.g. pull/push access for a repository. + if not provided it will return a refresh token that has full scope. + Note: you need to pin it down to the repository level, there is no wildcard available. + + examples: + repository:my-repository:pull,push + repository:my-repository:pull + + see docs for details: https://docs.docker.com/registry/spec/auth/scope/ + type: string + tenantId: + description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. + type: string + required: + - auth + - registry + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/clusterexternalsecret.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/clusterexternalsecret.yaml new file mode 100644 index 0000000000..6fdd831ed5 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/clusterexternalsecret.yaml @@ -0,0 +1,666 @@ +{{- if and (.Values.installCRDs) (.Values.crds.createClusterExternalSecret) }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: clusterexternalsecrets.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - externalsecrets + kind: ClusterExternalSecret + listKind: ClusterExternalSecretList + plural: clusterexternalsecrets + shortNames: + - ces + singular: clusterexternalsecret + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.externalSecretSpec.secretStoreRef.name + name: Store + type: string + - jsonPath: .spec.refreshTime + name: Refresh Interval + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ClusterExternalSecret is the Schema for the clusterexternalsecrets API. + 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: ClusterExternalSecretSpec defines the desired state of ClusterExternalSecret. + properties: + externalSecretMetadata: + description: The metadata of the external secrets to be created + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + externalSecretName: + description: The name of the external secrets to be created defaults to the name of the ClusterExternalSecret + type: string + externalSecretSpec: + description: The spec for the ExternalSecrets to be created + properties: + data: + description: Data defines the connection between the Kubernetes Secret keys and the Provider data + items: + description: ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.) and the Provider data. + properties: + remoteRef: + description: |- + RemoteRef points to the remote secret and defines + which secret (version/property/..) to fetch. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + metadataPolicy: + default: None + description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None + enum: + - None + - Fetch + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + secretKey: + description: |- + SecretKey defines the key in which the controller stores + the value. This is the key in the Kind=Secret + type: string + sourceRef: + description: |- + SourceRef allows you to override the source + from which the value will pulled from. + maxProperties: 1 + properties: + generatorRef: + description: |- + GeneratorRef points to a generator custom resource. + + Deprecated: The generatorRef is not implemented in .data[]. + this will be removed with v1. + properties: + apiVersion: + default: generators.external-secrets.io/v1alpha1 + description: Specify the apiVersion of the generator resource + type: string + kind: + description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc. + type: string + name: + description: Specify the name of the generator resource + type: string + required: + - kind + - name + type: object + storeRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + type: object + required: + - remoteRef + - secretKey + type: object + type: array + dataFrom: + description: |- + DataFrom is used to fetch all properties from a specific Provider data + If multiple entries are specified, the Secret keys are merged in the specified order + items: + properties: + extract: + description: |- + Used to extract multiple key/value pairs from one secret + Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + metadataPolicy: + default: None + description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None + enum: + - None + - Fetch + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + find: + description: |- + Used to find secrets based on tags or regular expressions + Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + name: + description: Finds secrets based on the name. + properties: + regexp: + description: Finds secrets base + type: string + type: object + path: + description: A root path to start the find operations. + type: string + tags: + additionalProperties: + type: string + description: Find secrets based on tags. + type: object + type: object + rewrite: + description: |- + Used to rewrite secret Keys after getting them from the secret Provider + Multiple Rewrite operations can be provided. They are applied in a layered order (first to last) + items: + properties: + regexp: + description: |- + Used to rewrite with regular expressions. + The resulting key will be the output of a regexp.ReplaceAll operation. + properties: + source: + description: Used to define the regular expression of a re.Compiler. + type: string + target: + description: Used to define the target pattern of a ReplaceAll operation. + type: string + required: + - source + - target + type: object + transform: + description: |- + Used to apply string transformation on the secrets. + The resulting key will be the output of the template applied by the operation. + properties: + template: + description: |- + Used to define the template to apply on the secret name. + `.value ` will specify the secret name in the template. + type: string + required: + - template + type: object + type: object + type: array + sourceRef: + description: |- + SourceRef points to a store or generator + which contains secret values ready to use. + Use this in combination with Extract or Find pull values out of + a specific SecretStore. + When sourceRef points to a generator Extract or Find is not supported. + The generator returns a static map of values + maxProperties: 1 + properties: + generatorRef: + description: GeneratorRef points to a generator custom resource. + properties: + apiVersion: + default: generators.external-secrets.io/v1alpha1 + description: Specify the apiVersion of the generator resource + type: string + kind: + description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc. + type: string + name: + description: Specify the name of the generator resource + type: string + required: + - kind + - name + type: object + storeRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + type: object + type: object + type: array + refreshInterval: + default: 1h + description: |- + RefreshInterval is the amount of time before the values are read again from the SecretStore provider + Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + May be set to zero to fetch and create it once. Defaults to 1h. + type: string + secretStoreRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + target: + default: + creationPolicy: Owner + deletionPolicy: Retain + description: |- + ExternalSecretTarget defines the Kubernetes Secret to be created + There can be only one target per ExternalSecret. + properties: + creationPolicy: + default: Owner + description: |- + CreationPolicy defines rules on how to create the resulting Secret + Defaults to 'Owner' + enum: + - Owner + - Orphan + - Merge + - None + type: string + deletionPolicy: + default: Retain + description: |- + DeletionPolicy defines rules on how to delete the resulting Secret + Defaults to 'Retain' + enum: + - Delete + - Merge + - Retain + type: string + immutable: + description: Immutable defines if the final secret will be immutable + type: boolean + name: + description: |- + Name defines the name of the Secret resource to be managed + This field is immutable + Defaults to the .metadata.name of the ExternalSecret resource + type: string + template: + description: Template defines a blueprint for the created Secret resource. + properties: + data: + additionalProperties: + type: string + type: object + engineVersion: + default: v2 + description: |- + EngineVersion specifies the template engine version + that should be used to compile/execute the + template specified in .data and .templateFrom[]. + enum: + - v1 + - v2 + type: string + mergePolicy: + default: Replace + enum: + - Replace + - Merge + type: string + metadata: + description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + templateFrom: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + literal: + type: string + secret: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + target: + default: Data + enum: + - Data + - Annotations + - Labels + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: object + namespaceSelector: + description: |- + The labels to select by to find the Namespaces to create the ExternalSecrets in. + Deprecated: Use NamespaceSelectors instead. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaceSelectors: + description: A list of labels to select by to find the Namespaces to create the ExternalSecrets in. The selectors are ORed. + items: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + namespaces: + description: Choose namespaces by name. This field is ORed with anything that NamespaceSelectors ends up choosing. + items: + type: string + type: array + refreshTime: + description: The time in which the controller should reconcile its objects and recheck namespaces for labels. + type: string + required: + - externalSecretSpec + type: object + status: + description: ClusterExternalSecretStatus defines the observed state of ClusterExternalSecret. + properties: + conditions: + items: + properties: + message: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + externalSecretName: + description: ExternalSecretName is the name of the ExternalSecrets created by the ClusterExternalSecret + type: string + failedNamespaces: + description: Failed namespaces are the namespaces that failed to apply an ExternalSecret + items: + description: ClusterExternalSecretNamespaceFailure represents a failed namespace deployment and it's reason. + properties: + namespace: + description: Namespace is the namespace that failed when trying to apply an ExternalSecret + type: string + reason: + description: Reason is why the ExternalSecret failed to apply to the namespace + type: string + required: + - namespace + type: object + type: array + provisionedNamespaces: + description: ProvisionedNamespaces are the namespaces where the ClusterExternalSecret has secrets + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/clustersecretstore.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/clustersecretstore.yaml new file mode 100644 index 0000000000..0c946b54eb --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/clustersecretstore.yaml @@ -0,0 +1,4601 @@ +{{- if and (.Values.installCRDs) (.Values.crds.createClusterSecretStore) }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: clustersecretstores.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - externalsecrets + kind: ClusterSecretStore + listKind: ClusterSecretStoreList + plural: clustersecretstores + shortNames: + - css + singular: clustersecretstore + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + deprecated: true + name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterSecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields. + 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: SecretStoreSpec defines the desired state of SecretStore. + properties: + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters ES based on this property + type: string + provider: + description: Used to configure the provider. Only one provider may be set + maxProperties: 1 + minProperties: 1 + properties: + akeyless: + description: Akeyless configures this store to sync secrets using Akeyless Vault provider + properties: + akeylessGWApiURL: + description: Akeyless GW API Url from which the secrets to be fetched from. + type: string + authSecretRef: + description: Auth configures how the operator authenticates with Akeyless. + properties: + kubernetesAuth: + description: |- + Kubernetes authenticates with Akeyless by passing the ServiceAccount + token stored in the named Secret resource. + properties: + accessID: + description: the Akeyless Kubernetes auth-method access-id + type: string + k8sConfName: + description: Kubernetes-auth configuration name in Akeyless-Gateway + type: string + secretRef: + description: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Akeyless. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Akeyless. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - accessID + - k8sConfName + type: object + secretRef: + description: |- + Reference to a Secret that contains the details + to authenticate with Akeyless. + properties: + accessID: + description: The SecretAccessID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessType: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessTypeParam: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + caBundle: + description: |- + PEM/base64 encoded CA bundle used to validate Akeyless Gateway certificate. Only used + if the AkeylessGWApiURL URL is using HTTPS protocol. If not set the system root certificates + are used to validate the TLS connection. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Akeyless Gateway certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + required: + - akeylessGWApiURL + - authSecretRef + type: object + alibaba: + description: Alibaba configures this store to sync secrets using Alibaba Cloud provider + properties: + auth: + description: AlibabaAuth contains a secretRef for credentials. + properties: + rrsa: + description: Authenticate against Alibaba using RRSA. + properties: + oidcProviderArn: + type: string + oidcTokenFilePath: + type: string + roleArn: + type: string + sessionName: + type: string + required: + - oidcProviderArn + - oidcTokenFilePath + - roleArn + - sessionName + type: object + secretRef: + description: AlibabaAuthSecretRef holds secret references for Alibaba credentials. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessKeySecretSecretRef: + description: The AccessKeySecret is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - accessKeyIDSecretRef + - accessKeySecretSecretRef + type: object + type: object + regionID: + description: Alibaba Region to be used for the provider + type: string + required: + - auth + - regionID + type: object + aws: + description: AWS configures this store to sync secrets using AWS Secret Manager provider + properties: + auth: + description: |- + Auth defines the information necessary to authenticate against AWS + if not set aws sdk will infer credentials from your environment + see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + properties: + jwt: + description: Authenticate against AWS using service account tokens. + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + secretRef: + description: |- + AWSAuthSecretRef holds secret references for AWS credentials + both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + region: + description: AWS Region to be used for the provider + type: string + role: + description: Role is a Role ARN which the SecretManager provider will assume + type: string + service: + description: Service defines which service should be used to fetch the secrets + enum: + - SecretsManager + - ParameterStore + type: string + required: + - region + - service + type: object + azurekv: + description: AzureKV configures this store to sync secrets using Azure Key Vault provider + properties: + authSecretRef: + description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. + properties: + clientId: + description: The Azure clientId of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: The Azure ClientSecret of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + authType: + default: ServicePrincipal + description: |- + Auth type defines how to authenticate to the keyvault service. + Valid values are: + - "ServicePrincipal" (default): Using a service principal (tenantId, clientId, clientSecret) + - "ManagedIdentity": Using Managed Identity assigned to the pod (see aad-pod-identity) + enum: + - ServicePrincipal + - ManagedIdentity + - WorkloadIdentity + type: string + identityId: + description: If multiple Managed Identity is assigned to the pod, you can select the one to be used + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + tenantId: + description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. + type: string + vaultUrl: + description: Vault Url from which the secrets to be fetched from. + type: string + required: + - vaultUrl + type: object + fake: + description: Fake configures a store with static key/value pairs + properties: + data: + items: + properties: + key: + type: string + value: + type: string + valueMap: + additionalProperties: + type: string + type: object + version: + type: string + required: + - key + type: object + type: array + required: + - data + type: object + gcpsm: + description: GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider + properties: + auth: + description: Auth defines the information necessary to authenticate against GCP + properties: + secretRef: + properties: + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + workloadIdentity: + properties: + clusterLocation: + type: string + clusterName: + type: string + clusterProjectID: + type: string + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - clusterLocation + - clusterName + - serviceAccountRef + type: object + type: object + projectID: + description: ProjectID project where secret is located + type: string + type: object + gitlab: + description: GitLab configures this store to sync secrets using GitLab Variables provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a GitLab instance. + properties: + SecretRef: + properties: + accessToken: + description: AccessToken is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - SecretRef + type: object + projectID: + description: ProjectID specifies a project where secrets are located. + type: string + url: + description: URL configures the GitLab instance URL. Defaults to https://gitlab.com/. + type: string + required: + - auth + type: object + ibm: + description: IBM configures this store to sync secrets using IBM Cloud provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the IBM secrets manager. + properties: + secretRef: + properties: + secretApiKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + serviceUrl: + description: ServiceURL is the Endpoint URL that is specific to the Secrets Manager service instance + type: string + required: + - auth + type: object + kubernetes: + description: Kubernetes configures this store to sync secrets using a Kubernetes cluster provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Kubernetes instance. + maxProperties: 1 + minProperties: 1 + properties: + cert: + description: has both clientCert and clientKey as secretKeySelector + properties: + clientCert: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientKey: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + serviceAccount: + description: points to a service account that should be used for authentication + properties: + serviceAccount: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + token: + description: use static token to authenticate with + properties: + bearerToken: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + remoteNamespace: + default: default + description: Remote namespace to fetch the secrets from + type: string + server: + description: configures the Kubernetes server Address. + properties: + caBundle: + description: CABundle is a base64-encoded CA certificate + format: byte + type: string + caProvider: + description: 'see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + default: kubernetes.default + description: configures the Kubernetes server Address. + type: string + type: object + required: + - auth + type: object + oracle: + description: Oracle configures this store to sync secrets using Oracle Vault provider + properties: + auth: + description: |- + Auth configures how secret-manager authenticates with the Oracle Vault. + If empty, instance principal is used. Optionally, the authenticating principal type + and/or user data may be supplied for the use of workload identity and user principal. + properties: + secretRef: + description: SecretRef to pass through sensitive information. + properties: + fingerprint: + description: Fingerprint is the fingerprint of the API private key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privatekey: + description: PrivateKey is the user's API Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - fingerprint + - privatekey + type: object + tenancy: + description: Tenancy is the tenancy OCID where user is located. + type: string + user: + description: User is an access OCID specific to the account. + type: string + required: + - secretRef + - tenancy + - user + type: object + compartment: + description: |- + Compartment is the vault compartment OCID. + Required for PushSecret + type: string + encryptionKey: + description: |- + EncryptionKey is the OCID of the encryption key within the vault. + Required for PushSecret + type: string + principalType: + description: |- + The type of principal to use for authentication. If left blank, the Auth struct will + determine the principal type. This optional field must be specified if using + workload identity. + enum: + - "" + - UserPrincipal + - InstancePrincipal + - Workload + type: string + region: + description: Region is the region where vault is located. + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + vault: + description: Vault is the vault's OCID of the specific vault where secret is located. + type: string + required: + - region + - vault + type: object + passworddepot: + description: Configures a store to sync secrets with a Password Depot instance. + properties: + auth: + description: Auth configures how secret-manager authenticates with a Password Depot instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + database: + description: Database to use as source + type: string + host: + description: URL configures the Password Depot instance URL. + type: string + required: + - auth + - database + - host + type: object + vault: + description: Vault configures this store to sync secrets using Hashi provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + default: approle + 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. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + - roleId + - secretRef + type: object + cert: + description: |- + Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate + Cert authentication method + properties: + clientCert: + description: |- + ClientCert is a certificate to authenticate using the Cert Vault + authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretRef: + description: |- + SecretRef to a key in a Secret resource containing client private key to + authenticate with Vault using the Cert authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + jwt: + description: |- + Jwt authenticates with Vault by passing role and JWT token using the + JWT/OIDC authentication method + properties: + kubernetesServiceAccountToken: + description: |- + Optional ServiceAccountToken specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Optional audiences field that will be used to request a temporary Kubernetes service + account token for the service account referenced by `serviceAccountRef`. + Defaults to a single audience `vault` it not specified. + items: + type: string + type: array + expirationSeconds: + description: |- + Optional expiration time in seconds that will be used to request a temporary + Kubernetes service account token for the service account referenced by + `serviceAccountRef`. + Defaults to 10 minutes. + format: int64 + type: integer + serviceAccountRef: + description: Service account field containing the name of a kubernetes ServiceAccount. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + path: + default: jwt + description: |- + Path where the JWT authentication backend is mounted + in Vault, e.g: "jwt" + type: string + role: + description: |- + Role is a JWT role to authenticate using the JWT/OIDC Vault + authentication method + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Vault using the JWT/OIDC authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + default: kubernetes + description: |- + Path where the Kubernetes authentication backend is mounted in Vault, e.g: + "kubernetes" + 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: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Vault. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - mountPath + - role + type: object + ldap: + description: |- + Ldap authenticates with Vault by passing username/password pair using + the LDAP authentication method + properties: + path: + default: ldap + description: |- + Path where the LDAP authentication backend is mounted + in Vault, e.g: "ldap" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the LDAP + user used to authenticate with Vault using the LDAP authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a LDAP user name used to authenticate using the LDAP Vault + authentication method + type: string + required: + - path + - username + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caBundle: + description: |- + PEM encoded CA bundle 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Vault server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + forwardInconsistent: + description: |- + ForwardInconsistent tells Vault to forward read-after-write requests to the Vault + leader instead of simply retrying within a loop. This can increase performance if + the option is enabled serverside. + https://www.vaultproject.io/docs/configuration/replication#allow_forwarding_via_header + type: boolean + 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 KV backend endpoint, e.g: + "secret". The v2 KV secret engine version specific "/data" path suffix + for fetching secrets from Vault is optional and will be appended + if not present in specified path. + type: string + readYourWrites: + description: |- + ReadYourWrites ensures isolated read-after-write semantics by + providing discovered cluster replication states in each request. + More information about eventual consistency in Vault can be found here + https://www.vaultproject.io/docs/enterprise/consistency + type: boolean + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + version: + default: v2 + description: |- + Version is the Vault KV secret engine version. This can be either "v1" or + "v2". Version defaults to "v2". + enum: + - v1 + - v2 + type: string + required: + - auth + - server + type: object + webhook: + description: Webhook configures this store to sync secrets using a generic templated webhook + properties: + body: + description: Body + type: string + caBundle: + description: |- + PEM encoded CA bundle used to validate webhook 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate webhook server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + headers: + additionalProperties: + type: string + description: Headers + type: object + method: + description: Webhook Method + type: string + result: + description: Result formatting + properties: + jsonPath: + description: Json path of return value + type: string + type: object + secrets: + description: |- + Secrets to fill in templates + These secrets will be passed to the templating function as key value pairs under the given name + items: + properties: + name: + description: Name of this secret in templates + type: string + secretRef: + description: Secret ref to fill in credentials + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - name + - secretRef + type: object + type: array + timeout: + description: Timeout + type: string + url: + description: Webhook url to call + type: string + required: + - result + - url + type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets using Yandex Lockbox provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + type: object + retrySettings: + description: Used to configure http retries if failed + properties: + maxRetries: + format: int32 + type: integer + retryInterval: + type: string + type: object + required: + - provider + type: object + status: + description: SecretStoreStatus defines the observed state of the SecretStore. + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + - jsonPath: .status.capabilities + name: Capabilities + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ClusterSecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields. + 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: SecretStoreSpec defines the desired state of SecretStore. + properties: + conditions: + description: Used to constraint a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore + items: + description: |- + ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in + for a ClusterSecretStore instance. + properties: + namespaceRegexes: + description: Choose namespaces by using regex matching + items: + type: string + type: array + namespaceSelector: + description: Choose namespace using a labelSelector + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Choose namespaces by name + items: + type: string + type: array + type: object + type: array + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters ES based on this property + type: string + provider: + description: Used to configure the provider. Only one provider may be set + maxProperties: 1 + minProperties: 1 + properties: + akeyless: + description: Akeyless configures this store to sync secrets using Akeyless Vault provider + properties: + akeylessGWApiURL: + description: Akeyless GW API Url from which the secrets to be fetched from. + type: string + authSecretRef: + description: Auth configures how the operator authenticates with Akeyless. + properties: + kubernetesAuth: + description: |- + Kubernetes authenticates with Akeyless by passing the ServiceAccount + token stored in the named Secret resource. + properties: + accessID: + description: the Akeyless Kubernetes auth-method access-id + type: string + k8sConfName: + description: Kubernetes-auth configuration name in Akeyless-Gateway + type: string + secretRef: + description: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Akeyless. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Akeyless. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - accessID + - k8sConfName + type: object + secretRef: + description: |- + Reference to a Secret that contains the details + to authenticate with Akeyless. + properties: + accessID: + description: The SecretAccessID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessType: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessTypeParam: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + caBundle: + description: |- + PEM/base64 encoded CA bundle used to validate Akeyless Gateway certificate. Only used + if the AkeylessGWApiURL URL is using HTTPS protocol. If not set the system root certificates + are used to validate the TLS connection. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Akeyless Gateway certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + required: + - akeylessGWApiURL + - authSecretRef + type: object + alibaba: + description: Alibaba configures this store to sync secrets using Alibaba Cloud provider + properties: + auth: + description: AlibabaAuth contains a secretRef for credentials. + properties: + rrsa: + description: Authenticate against Alibaba using RRSA. + properties: + oidcProviderArn: + type: string + oidcTokenFilePath: + type: string + roleArn: + type: string + sessionName: + type: string + required: + - oidcProviderArn + - oidcTokenFilePath + - roleArn + - sessionName + type: object + secretRef: + description: AlibabaAuthSecretRef holds secret references for Alibaba credentials. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessKeySecretSecretRef: + description: The AccessKeySecret is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - accessKeyIDSecretRef + - accessKeySecretSecretRef + type: object + type: object + regionID: + description: Alibaba Region to be used for the provider + type: string + required: + - auth + - regionID + type: object + aws: + description: AWS configures this store to sync secrets using AWS Secret Manager provider + properties: + additionalRoles: + description: AdditionalRoles is a chained list of Role ARNs which the provider will sequentially assume before assuming the Role + items: + type: string + type: array + auth: + description: |- + Auth defines the information necessary to authenticate against AWS + if not set aws sdk will infer credentials from your environment + see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + properties: + jwt: + description: Authenticate against AWS using service account tokens. + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + secretRef: + description: |- + AWSAuthSecretRef holds secret references for AWS credentials + both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + externalID: + description: AWS External ID set on assumed IAM roles + type: string + prefix: + description: Prefix adds a prefix to all retrieved values. + type: string + region: + description: AWS Region to be used for the provider + type: string + role: + description: Role is a Role ARN which the provider will assume + type: string + secretsManager: + description: SecretsManager defines how the provider behaves when interacting with AWS SecretsManager + properties: + forceDeleteWithoutRecovery: + description: |- + Specifies whether to delete the secret without any recovery window. You + can't use both this parameter and RecoveryWindowInDays in the same call. + If you don't use either, then by default Secrets Manager uses a 30 day + recovery window. + see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-ForceDeleteWithoutRecovery + type: boolean + recoveryWindowInDays: + description: |- + The number of days from 7 to 30 that Secrets Manager waits before + permanently deleting the secret. You can't use both this parameter and + ForceDeleteWithoutRecovery in the same call. If you don't use either, + then by default Secrets Manager uses a 30 day recovery window. + see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays + format: int64 + type: integer + type: object + service: + description: Service defines which service should be used to fetch the secrets + enum: + - SecretsManager + - ParameterStore + type: string + sessionTags: + description: AWS STS assume role session tags + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + transitiveTagKeys: + description: AWS STS assume role transitive session tags. Required when multiple rules are used with the provider + items: + type: string + type: array + required: + - region + - service + type: object + azurekv: + description: AzureKV configures this store to sync secrets using Azure Key Vault provider + properties: + authSecretRef: + description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity. + properties: + clientCertificate: + description: The Azure ClientCertificate of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientId: + description: The Azure clientId of the service principle or managed identity used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: The Azure ClientSecret of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + tenantId: + description: The Azure tenantId of the managed identity used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + authType: + default: ServicePrincipal + description: |- + Auth type defines how to authenticate to the keyvault service. + Valid values are: + - "ServicePrincipal" (default): Using a service principal (tenantId, clientId, clientSecret) + - "ManagedIdentity": Using Managed Identity assigned to the pod (see aad-pod-identity) + enum: + - ServicePrincipal + - ManagedIdentity + - WorkloadIdentity + type: string + environmentType: + default: PublicCloud + description: |- + EnvironmentType specifies the Azure cloud environment endpoints to use for + connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. + The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 + PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud + enum: + - PublicCloud + - USGovernmentCloud + - ChinaCloud + - GermanCloud + type: string + identityId: + description: If multiple Managed Identity is assigned to the pod, you can select the one to be used + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + tenantId: + description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity. + type: string + vaultUrl: + description: Vault Url from which the secrets to be fetched from. + type: string + required: + - vaultUrl + type: object + beyondtrust: + description: Beyondtrust configures this store to sync secrets using Password Safe provider. + properties: + auth: + description: Auth configures how the operator authenticates with Beyondtrust. + properties: + certificate: + description: Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + certificateKey: + description: Certificate private key (key.pem). For use when authenticating with an OAuth client Id + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientId: + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientSecret: + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - clientId + - clientSecret + type: object + server: + description: Auth configures how API server works. + properties: + apiUrl: + type: string + clientTimeOutSeconds: + description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds. + type: integer + retrievalType: + description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system. + type: string + separator: + description: A character that separates the folder names. + type: string + verifyCA: + type: boolean + required: + - apiUrl + - verifyCA + type: object + required: + - auth + - server + type: object + bitwardensecretsmanager: + description: BitwardenSecretsManager configures this store to sync secrets using BitwardenSecretsManager provider + properties: + apiURL: + type: string + auth: + description: |- + Auth configures how secret-manager authenticates with a bitwarden machine account instance. + Make sure that the token being used has permissions on the given secret. + properties: + secretRef: + description: BitwardenSecretsManagerSecretRef contains the credential ref to the bitwarden instance. + properties: + credentials: + description: AccessToken used for the bitwarden instance. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - credentials + type: object + required: + - secretRef + type: object + bitwardenServerSDKURL: + type: string + caBundle: + description: |- + Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack + can be performed. + type: string + caProvider: + description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + identityURL: + type: string + organizationID: + description: OrganizationID determines which organization this secret store manages. + type: string + projectID: + description: ProjectID determines which project this secret store manages. + type: string + required: + - auth + - organizationID + - projectID + type: object + chef: + description: Chef configures this store to sync secrets with chef server + properties: + auth: + description: Auth defines the information necessary to authenticate against chef Server + properties: + secretRef: + description: ChefAuthSecretRef holds secret references for chef server login credentials. + properties: + privateKeySecretRef: + description: SecretKey is the Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - privateKeySecretRef + type: object + required: + - secretRef + type: object + serverUrl: + description: ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/" + type: string + username: + description: UserName should be the user ID on the chef server + type: string + required: + - auth + - serverUrl + - username + type: object + conjur: + description: Conjur configures this store to sync secrets using conjur provider + properties: + auth: + properties: + apikey: + properties: + account: + type: string + apiKeyRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + userRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - account + - apiKeyRef + - userRef + type: object + jwt: + properties: + account: + type: string + hostId: + description: |- + Optional HostID for JWT authentication. This may be used depending + on how the Conjur JWT authenticator policy is configured. + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Conjur using the JWT authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional ServiceAccountRef specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + serviceID: + description: The conjur authn jwt webservice id + type: string + required: + - account + - serviceID + type: object + type: object + caBundle: + type: string + caProvider: + description: |- + Used to provide custom certificate authority (CA) certificates + for a secret store. The CAProvider points to a Secret or ConfigMap resource + that contains a PEM-encoded certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + type: string + required: + - auth + - url + type: object + delinea: + description: |- + Delinea DevOps Secrets Vault + https://docs.delinea.com/online-help/products/devops-secrets-vault/current + properties: + clientId: + description: ClientID is the non-secret part of the credential. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientSecret: + description: ClientSecret is the secret part of the credential. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + tenant: + description: Tenant is the chosen hostname / site name. + type: string + tld: + description: |- + TLD is based on the server location that was chosen during provisioning. + If unset, defaults to "com". + type: string + urlTemplate: + description: |- + URLTemplate + If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s". + type: string + required: + - clientId + - clientSecret + - tenant + type: object + device42: + description: Device42 configures this store to sync secrets using the Device42 provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Device42 instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + host: + description: URL configures the Device42 instance URL. + type: string + required: + - auth + - host + type: object + doppler: + description: Doppler configures this store to sync secrets using the Doppler provider + properties: + auth: + description: Auth configures how the Operator authenticates with the Doppler API + properties: + secretRef: + properties: + dopplerToken: + description: |- + The DopplerToken is used for authentication. + See https://docs.doppler.com/reference/api#authentication for auth token types. + The Key attribute defaults to dopplerToken if not specified. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - dopplerToken + type: object + required: + - secretRef + type: object + config: + description: Doppler config (required if not using a Service Token) + type: string + format: + description: Format enables the downloading of secrets as a file (string) + enum: + - json + - dotnet-json + - env + - yaml + - docker + type: string + nameTransformer: + description: Environment variable compatible name transforms that change secret names to a different format + enum: + - upper-camel + - camel + - lower-snake + - tf-var + - dotnet-env + - lower-kebab + type: string + project: + description: Doppler project (required if not using a Service Token) + type: string + required: + - auth + type: object + fake: + description: Fake configures a store with static key/value pairs + properties: + data: + items: + properties: + key: + type: string + value: + type: string + valueMap: + additionalProperties: + type: string + description: 'Deprecated: ValueMap is deprecated and is intended to be removed in the future, use the `value` field instead.' + type: object + version: + type: string + required: + - key + type: object + type: array + required: + - data + type: object + fortanix: + description: Fortanix configures this store to sync secrets using the Fortanix provider + properties: + apiKey: + description: APIKey is the API token to access SDKMS Applications. + properties: + secretRef: + description: SecretRef is a reference to a secret containing the SDKMS API Key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + apiUrl: + description: APIURL is the URL of SDKMS API. Defaults to `sdkms.fortanix.com`. + type: string + type: object + gcpsm: + description: GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider + properties: + auth: + description: Auth defines the information necessary to authenticate against GCP + properties: + secretRef: + properties: + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + workloadIdentity: + properties: + clusterLocation: + type: string + clusterName: + type: string + clusterProjectID: + type: string + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - clusterLocation + - clusterName + - serviceAccountRef + type: object + type: object + location: + description: Location optionally defines a location for a secret + type: string + projectID: + description: ProjectID project where secret is located + type: string + type: object + gitlab: + description: GitLab configures this store to sync secrets using GitLab Variables provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a GitLab instance. + properties: + SecretRef: + properties: + accessToken: + description: AccessToken is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - SecretRef + type: object + environment: + description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments) + type: string + groupIDs: + description: GroupIDs specify, which gitlab groups to pull secrets from. Group secrets are read from left to right followed by the project variables. + items: + type: string + type: array + inheritFromGroups: + description: InheritFromGroups specifies whether parent groups should be discovered and checked for secrets. + type: boolean + projectID: + description: ProjectID specifies a project where secrets are located. + type: string + url: + description: URL configures the GitLab instance URL. Defaults to https://gitlab.com/. + type: string + required: + - auth + type: object + ibm: + description: IBM configures this store to sync secrets using IBM Cloud provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the IBM secrets manager. + maxProperties: 1 + minProperties: 1 + properties: + containerAuth: + description: IBM Container-based auth with IAM Trusted Profile. + properties: + iamEndpoint: + type: string + profile: + description: the IBM Trusted Profile + type: string + tokenLocation: + description: Location the token is mounted on the pod + type: string + required: + - profile + type: object + secretRef: + properties: + secretApiKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + serviceUrl: + description: ServiceURL is the Endpoint URL that is specific to the Secrets Manager service instance + type: string + required: + - auth + type: object + infisical: + description: Infisical configures this store to sync secrets using the Infisical provider + properties: + auth: + description: Auth configures how the Operator authenticates with the Infisical API + properties: + universalAuthCredentials: + properties: + clientId: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - clientId + - clientSecret + type: object + type: object + hostAPI: + default: https://app.infisical.com/api + type: string + secretsScope: + properties: + environmentSlug: + type: string + projectSlug: + type: string + secretsPath: + default: / + type: string + required: + - environmentSlug + - projectSlug + type: object + required: + - auth + - secretsScope + type: object + keepersecurity: + description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider + properties: + authRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + folderID: + type: string + required: + - authRef + - folderID + type: object + kubernetes: + description: Kubernetes configures this store to sync secrets using a Kubernetes cluster provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Kubernetes instance. + maxProperties: 1 + minProperties: 1 + properties: + cert: + description: has both clientCert and clientKey as secretKeySelector + properties: + clientCert: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientKey: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + serviceAccount: + description: points to a service account that should be used for authentication + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + token: + description: use static token to authenticate with + properties: + bearerToken: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + authRef: + description: A reference to a secret that contains the auth information. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + remoteNamespace: + default: default + description: Remote namespace to fetch the secrets from + type: string + server: + description: configures the Kubernetes server Address. + properties: + caBundle: + description: CABundle is a base64-encoded CA certificate + format: byte + type: string + caProvider: + description: 'see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + default: kubernetes.default + description: configures the Kubernetes server Address. + type: string + type: object + type: object + onboardbase: + description: Onboardbase configures this store to sync secrets using the Onboardbase provider + properties: + apiHost: + default: https://public.onboardbase.com/api/v1/ + description: APIHost use this to configure the host url for the API for selfhosted installation, default is https://public.onboardbase.com/api/v1/ + type: string + auth: + description: Auth configures how the Operator authenticates with the Onboardbase API + properties: + apiKeyRef: + description: |- + OnboardbaseAPIKey is the APIKey generated by an admin account. + It is used to recognize and authorize access to a project and environment within onboardbase + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + passcodeRef: + description: OnboardbasePasscode is the passcode attached to the API Key + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - apiKeyRef + - passcodeRef + type: object + environment: + default: development + description: Environment is the name of an environmnent within a project to pull the secrets from + type: string + project: + default: development + description: Project is an onboardbase project that the secrets should be pulled from + type: string + required: + - apiHost + - auth + - environment + - project + type: object + onepassword: + description: OnePassword configures this store to sync secrets using the 1Password Cloud provider + properties: + auth: + description: Auth defines the information necessary to authenticate against OnePassword Connect Server + properties: + secretRef: + description: OnePasswordAuthSecretRef holds secret references for 1Password credentials. + properties: + connectTokenSecretRef: + description: The ConnectToken is used for authentication to a 1Password Connect Server. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - connectTokenSecretRef + type: object + required: + - secretRef + type: object + connectHost: + description: ConnectHost defines the OnePassword Connect Server to connect to + type: string + vaults: + additionalProperties: + type: integer + description: Vaults defines which OnePassword vaults to search in which order + type: object + required: + - auth + - connectHost + - vaults + type: object + oracle: + description: Oracle configures this store to sync secrets using Oracle Vault provider + properties: + auth: + description: |- + Auth configures how secret-manager authenticates with the Oracle Vault. + If empty, use the instance principal, otherwise the user credentials specified in Auth. + properties: + secretRef: + description: SecretRef to pass through sensitive information. + properties: + fingerprint: + description: Fingerprint is the fingerprint of the API private key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privatekey: + description: PrivateKey is the user's API Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - fingerprint + - privatekey + type: object + tenancy: + description: Tenancy is the tenancy OCID where user is located. + type: string + user: + description: User is an access OCID specific to the account. + type: string + required: + - secretRef + - tenancy + - user + type: object + compartment: + description: |- + Compartment is the vault compartment OCID. + Required for PushSecret + type: string + encryptionKey: + description: |- + EncryptionKey is the OCID of the encryption key within the vault. + Required for PushSecret + type: string + principalType: + description: |- + The type of principal to use for authentication. If left blank, the Auth struct will + determine the principal type. This optional field must be specified if using + workload identity. + enum: + - "" + - UserPrincipal + - InstancePrincipal + - Workload + type: string + region: + description: Region is the region where vault is located. + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + vault: + description: Vault is the vault's OCID of the specific vault where secret is located. + type: string + required: + - region + - vault + type: object + passbolt: + properties: + auth: + description: Auth defines the information necessary to authenticate against Passbolt Server + properties: + passwordSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privateKeySecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - passwordSecretRef + - privateKeySecretRef + type: object + host: + description: Host defines the Passbolt Server to connect to + type: string + required: + - auth + - host + type: object + passworddepot: + description: Configures a store to sync secrets with a Password Depot instance. + properties: + auth: + description: Auth configures how secret-manager authenticates with a Password Depot instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + database: + description: Database to use as source + type: string + host: + description: URL configures the Password Depot instance URL. + type: string + required: + - auth + - database + - host + type: object + pulumi: + description: Pulumi configures this store to sync secrets using the Pulumi provider + properties: + accessToken: + description: AccessToken is the access tokens to sign in to the Pulumi Cloud Console. + properties: + secretRef: + description: SecretRef is a reference to a secret containing the Pulumi API token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + apiUrl: + default: https://api.pulumi.com/api/preview + description: APIURL is the URL of the Pulumi API. + type: string + environment: + description: |- + Environment are YAML documents composed of static key-value pairs, programmatic expressions, + dynamically retrieved values from supported providers including all major clouds, + and other Pulumi ESC environments. + To create a new environment, visit https://www.pulumi.com/docs/esc/environments/ for more information. + type: string + organization: + description: |- + Organization are a space to collaborate on shared projects and stacks. + To create a new organization, visit https://app.pulumi.com/ and click "New Organization". + type: string + required: + - accessToken + - environment + - organization + type: object + scaleway: + description: Scaleway + properties: + accessKey: + description: AccessKey is the non-secret part of the api key. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + apiUrl: + description: APIURL is the url of the api to use. Defaults to https://api.scaleway.com + type: string + projectId: + description: 'ProjectID is the id of your project, which you can find in the console: https://console.scaleway.com/project/settings' + type: string + region: + description: 'Region where your secrets are located: https://developers.scaleway.com/en/quickstart/#region-and-zone' + type: string + secretKey: + description: SecretKey is the non-secret part of the api key. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - accessKey + - projectId + - region + - secretKey + type: object + secretserver: + description: |- + SecretServer configures this store to sync secrets using SecretServer provider + https://docs.delinea.com/online-help/secret-server/start.htm + properties: + password: + description: Password is the secret server account password. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + serverURL: + description: |- + ServerURL + URL to your secret server installation + type: string + username: + description: Username is the secret server account username. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - password + - serverURL + - username + type: object + senhasegura: + description: Senhasegura configures this store to sync secrets using senhasegura provider + properties: + auth: + description: Auth defines parameters to authenticate in senhasegura + properties: + clientId: + type: string + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - clientId + - clientSecretSecretRef + type: object + ignoreSslCertificate: + default: false + description: IgnoreSslCertificate defines if SSL certificate must be ignored + type: boolean + module: + description: Module defines which senhasegura module should be used to get secrets + type: string + url: + description: URL of senhasegura + type: string + required: + - auth + - module + - url + type: object + vault: + description: Vault configures this store to sync secrets using Hashi provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + default: approle + 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 + roleRef: + description: |- + Reference to a key in a Secret that contains the App Role ID 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 id. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + 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. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + - secretRef + type: object + cert: + description: |- + Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate + Cert authentication method + properties: + clientCert: + description: |- + ClientCert is a certificate to authenticate using the Cert Vault + authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretRef: + description: |- + SecretRef to a key in a Secret resource containing client private key to + authenticate with Vault using the Cert authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + iam: + description: |- + Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials + AWS IAM authentication method + properties: + externalID: + description: AWS External ID set on assumed IAM roles + type: string + jwt: + description: Specify a service account with IRSA enabled + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + path: + description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"' + type: string + region: + description: AWS region + type: string + role: + description: This is the AWS role to be assumed before talking to vault + type: string + secretRef: + description: Specify credentials in a Secret object + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + vaultAwsIamServerID: + description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws' + type: string + vaultRole: + description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine + type: string + required: + - vaultRole + type: object + jwt: + description: |- + Jwt authenticates with Vault by passing role and JWT token using the + JWT/OIDC authentication method + properties: + kubernetesServiceAccountToken: + description: |- + Optional ServiceAccountToken specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Optional audiences field that will be used to request a temporary Kubernetes service + account token for the service account referenced by `serviceAccountRef`. + Defaults to a single audience `vault` it not specified. + Deprecated: use serviceAccountRef.Audiences instead + items: + type: string + type: array + expirationSeconds: + description: |- + Optional expiration time in seconds that will be used to request a temporary + Kubernetes service account token for the service account referenced by + `serviceAccountRef`. + Deprecated: this will be removed in the future. + Defaults to 10 minutes. + format: int64 + type: integer + serviceAccountRef: + description: Service account field containing the name of a kubernetes ServiceAccount. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + path: + default: jwt + description: |- + Path where the JWT authentication backend is mounted + in Vault, e.g: "jwt" + type: string + role: + description: |- + Role is a JWT role to authenticate using the JWT/OIDC Vault + authentication method + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Vault using the JWT/OIDC authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + default: kubernetes + description: |- + Path where the Kubernetes authentication backend is mounted in Vault, e.g: + "kubernetes" + 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: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Vault. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - mountPath + - role + type: object + ldap: + description: |- + Ldap authenticates with Vault by passing username/password pair using + the LDAP authentication method + properties: + path: + default: ldap + description: |- + Path where the LDAP authentication backend is mounted + in Vault, e.g: "ldap" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the LDAP + user used to authenticate with Vault using the LDAP authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a LDAP user name used to authenticate using the LDAP Vault + authentication method + type: string + required: + - path + - username + type: object + namespace: + description: |- + Name of the vault namespace to authenticate to. This can be different than the namespace your secret is in. + 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 + This will default to Vault.Namespace field if set, or empty otherwise + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + userPass: + description: UserPass authenticates with Vault by passing username/password pair + properties: + path: + default: user + description: |- + Path where the UserPassword authentication backend is mounted + in Vault, e.g: "user" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the + user used to authenticate with Vault using the UserPass authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a user name used to authenticate using the UserPass Vault + authentication method + type: string + required: + - path + - username + type: object + type: object + caBundle: + description: |- + PEM encoded CA bundle 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Vault server certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + forwardInconsistent: + description: |- + ForwardInconsistent tells Vault to forward read-after-write requests to the Vault + leader instead of simply retrying within a loop. This can increase performance if + the option is enabled serverside. + https://www.vaultproject.io/docs/configuration/replication#allow_forwarding_via_header + type: boolean + headers: + additionalProperties: + type: string + description: Headers to be added in Vault request + type: object + 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 KV backend endpoint, e.g: + "secret". The v2 KV secret engine version specific "/data" path suffix + for fetching secrets from Vault is optional and will be appended + if not present in specified path. + type: string + readYourWrites: + description: |- + ReadYourWrites ensures isolated read-after-write semantics by + providing discovered cluster replication states in each request. + More information about eventual consistency in Vault can be found here + https://www.vaultproject.io/docs/enterprise/consistency + type: boolean + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + tls: + description: |- + The configuration used for client side related TLS communication, when the Vault server + requires mutual authentication. Only used if the Server URL is using HTTPS protocol. + This parameter is ignored for plain HTTP protocol connection. + It's worth noting this configuration is different from the "TLS certificates auth method", + which is available under the `auth.cert` section. + properties: + certSecretRef: + description: |- + CertSecretRef is a certificate added to the transport layer + when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.crt'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + keySecretRef: + description: |- + KeySecretRef to a key in a Secret resource containing client private key + added to the transport layer when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.key'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + version: + default: v2 + description: |- + Version is the Vault KV secret engine version. This can be either "v1" or + "v2". Version defaults to "v2". + enum: + - v1 + - v2 + type: string + required: + - auth + - server + type: object + webhook: + description: Webhook configures this store to sync secrets using a generic templated webhook + properties: + body: + description: Body + type: string + caBundle: + description: |- + PEM encoded CA bundle used to validate webhook 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate webhook server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + headers: + additionalProperties: + type: string + description: Headers + type: object + method: + description: Webhook Method + type: string + result: + description: Result formatting + properties: + jsonPath: + description: Json path of return value + type: string + type: object + secrets: + description: |- + Secrets to fill in templates + These secrets will be passed to the templating function as key value pairs under the given name + items: + properties: + name: + description: Name of this secret in templates + type: string + secretRef: + description: Secret ref to fill in credentials + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - name + - secretRef + type: object + type: array + timeout: + description: Timeout + type: string + url: + description: Webhook url to call + type: string + required: + - result + - url + type: object + yandexcertificatemanager: + description: YandexCertificateManager configures this store to sync secrets using Yandex Certificate Manager provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Certificate Manager + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets using Yandex Lockbox provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + type: object + refreshInterval: + description: Used to configure store refresh interval in seconds. Empty or 0 will default to the controller config. + type: integer + retrySettings: + description: Used to configure http retries if failed + properties: + maxRetries: + format: int32 + type: integer + retryInterval: + type: string + type: object + required: + - provider + type: object + status: + description: SecretStoreStatus defines the observed state of the SecretStore. + properties: + capabilities: + description: SecretStoreCapabilities defines the possible operations a SecretStore can do. + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/ecrauthorizationtoken.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/ecrauthorizationtoken.yaml new file mode 100644 index 0000000000..f015e114de --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/ecrauthorizationtoken.yaml @@ -0,0 +1,177 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: ecrauthorizationtokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - ecrauthorizationtoken + kind: ECRAuthorizationToken + listKind: ECRAuthorizationTokenList + plural: ecrauthorizationtokens + shortNames: + - ecrauthorizationtoken + singular: ecrauthorizationtoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ECRAuthorizationTokenSpec uses the GetAuthorizationToken API to retrieve an + authorization token. + The authorization token is valid for 12 hours. + The authorizationToken returned is a base64 encoded string that can be decoded + and used in a docker login command to authenticate to a registry. + For more information, see Registry authentication (https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth) in the Amazon Elastic Container Registry User Guide. + 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: + properties: + auth: + description: Auth defines how to authenticate with AWS + properties: + jwt: + description: Authenticate against AWS using service account tokens. + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + secretRef: + description: |- + AWSAuthSecretRef holds secret references for AWS credentials + both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + region: + description: Region specifies the region to operate in. + type: string + role: + description: |- + You can assume a role before making calls to the + desired AWS service. + type: string + required: + - region + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/externalsecret.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/externalsecret.yaml new file mode 100644 index 0000000000..817690be95 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/externalsecret.yaml @@ -0,0 +1,820 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: externalsecrets.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - externalsecrets + kind: ExternalSecret + listKind: ExternalSecretList + plural: externalsecrets + shortNames: + - es + singular: externalsecret + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.secretStoreRef.name + name: Store + type: string + - jsonPath: .spec.refreshInterval + name: Refresh Interval + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + deprecated: true + name: v1alpha1 + schema: + openAPIV3Schema: + description: ExternalSecret is the Schema for the external-secrets API. + 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: ExternalSecretSpec defines the desired state of ExternalSecret. + properties: + data: + description: Data defines the connection between the Kubernetes Secret keys and the Provider data + items: + description: ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.) and the Provider data. + properties: + remoteRef: + description: ExternalSecretDataRemoteRef defines Provider data location. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + secretKey: + type: string + required: + - remoteRef + - secretKey + type: object + type: array + dataFrom: + description: |- + DataFrom is used to fetch all properties from a specific Provider data + If multiple entries are specified, the Secret keys are merged in the specified order + items: + description: ExternalSecretDataRemoteRef defines Provider data location. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + type: array + refreshInterval: + default: 1h + description: |- + RefreshInterval is the amount of time before the values are read again from the SecretStore provider + Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + May be set to zero to fetch and create it once. Defaults to 1h. + type: string + secretStoreRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + target: + description: |- + ExternalSecretTarget defines the Kubernetes Secret to be created + There can be only one target per ExternalSecret. + properties: + creationPolicy: + default: Owner + description: |- + CreationPolicy defines rules on how to create the resulting Secret + Defaults to 'Owner' + enum: + - Owner + - Merge + - None + type: string + immutable: + description: Immutable defines if the final secret will be immutable + type: boolean + name: + description: |- + Name defines the name of the Secret resource to be managed + This field is immutable + Defaults to the .metadata.name of the ExternalSecret resource + type: string + template: + description: Template defines a blueprint for the created Secret resource. + properties: + data: + additionalProperties: + type: string + type: object + engineVersion: + default: v1 + description: |- + EngineVersion specifies the template engine version + that should be used to compile/execute the + template specified in .data and .templateFrom[]. + enum: + - v1 + - v2 + type: string + metadata: + description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + templateFrom: + items: + maxProperties: 1 + minProperties: 1 + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + type: object + type: array + type: + type: string + type: object + type: object + required: + - secretStoreRef + - target + type: object + status: + properties: + binding: + description: Binding represents a servicebinding.io Provisioned Service reference to the secret + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + refreshTime: + description: |- + refreshTime is the time and date the external secret was fetched and + the target secret updated + format: date-time + nullable: true + type: string + syncedResourceVersion: + description: SyncedResourceVersion keeps track of the last synced version + type: string + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.secretStoreRef.name + name: Store + type: string + - jsonPath: .spec.refreshInterval + name: Refresh Interval + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: ExternalSecret is the Schema for the external-secrets API. + 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: ExternalSecretSpec defines the desired state of ExternalSecret. + properties: + data: + description: Data defines the connection between the Kubernetes Secret keys and the Provider data + items: + description: ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.) and the Provider data. + properties: + remoteRef: + description: |- + RemoteRef points to the remote secret and defines + which secret (version/property/..) to fetch. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + metadataPolicy: + default: None + description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None + enum: + - None + - Fetch + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + secretKey: + description: |- + SecretKey defines the key in which the controller stores + the value. This is the key in the Kind=Secret + type: string + sourceRef: + description: |- + SourceRef allows you to override the source + from which the value will pulled from. + maxProperties: 1 + properties: + generatorRef: + description: |- + GeneratorRef points to a generator custom resource. + + Deprecated: The generatorRef is not implemented in .data[]. + this will be removed with v1. + properties: + apiVersion: + default: generators.external-secrets.io/v1alpha1 + description: Specify the apiVersion of the generator resource + type: string + kind: + description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc. + type: string + name: + description: Specify the name of the generator resource + type: string + required: + - kind + - name + type: object + storeRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + type: object + required: + - remoteRef + - secretKey + type: object + type: array + dataFrom: + description: |- + DataFrom is used to fetch all properties from a specific Provider data + If multiple entries are specified, the Secret keys are merged in the specified order + items: + properties: + extract: + description: |- + Used to extract multiple key/value pairs from one secret + Note: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + key: + description: Key is the key used in the Provider, mandatory + type: string + metadataPolicy: + default: None + description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None + enum: + - None + - Fetch + type: string + property: + description: Used to select a specific property of the Provider value (if a map), if supported + type: string + version: + description: Used to select a specific version of the Provider value, if supported + type: string + required: + - key + type: object + find: + description: |- + Used to find secrets based on tags or regular expressions + Note: Find does not support sourceRef.Generator or sourceRef.GeneratorRef. + properties: + conversionStrategy: + default: Default + description: Used to define a conversion Strategy + enum: + - Default + - Unicode + type: string + decodingStrategy: + default: None + description: Used to define a decoding Strategy + enum: + - Auto + - Base64 + - Base64URL + - None + type: string + name: + description: Finds secrets based on the name. + properties: + regexp: + description: Finds secrets base + type: string + type: object + path: + description: A root path to start the find operations. + type: string + tags: + additionalProperties: + type: string + description: Find secrets based on tags. + type: object + type: object + rewrite: + description: |- + Used to rewrite secret Keys after getting them from the secret Provider + Multiple Rewrite operations can be provided. They are applied in a layered order (first to last) + items: + properties: + regexp: + description: |- + Used to rewrite with regular expressions. + The resulting key will be the output of a regexp.ReplaceAll operation. + properties: + source: + description: Used to define the regular expression of a re.Compiler. + type: string + target: + description: Used to define the target pattern of a ReplaceAll operation. + type: string + required: + - source + - target + type: object + transform: + description: |- + Used to apply string transformation on the secrets. + The resulting key will be the output of the template applied by the operation. + properties: + template: + description: |- + Used to define the template to apply on the secret name. + `.value ` will specify the secret name in the template. + type: string + required: + - template + type: object + type: object + type: array + sourceRef: + description: |- + SourceRef points to a store or generator + which contains secret values ready to use. + Use this in combination with Extract or Find pull values out of + a specific SecretStore. + When sourceRef points to a generator Extract or Find is not supported. + The generator returns a static map of values + maxProperties: 1 + properties: + generatorRef: + description: GeneratorRef points to a generator custom resource. + properties: + apiVersion: + default: generators.external-secrets.io/v1alpha1 + description: Specify the apiVersion of the generator resource + type: string + kind: + description: Specify the Kind of the resource, e.g. Password, ACRAccessToken etc. + type: string + name: + description: Specify the name of the generator resource + type: string + required: + - kind + - name + type: object + storeRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + type: object + type: object + type: array + refreshInterval: + default: 1h + description: |- + RefreshInterval is the amount of time before the values are read again from the SecretStore provider + Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h" + May be set to zero to fetch and create it once. Defaults to 1h. + type: string + secretStoreRef: + description: SecretStoreRef defines which SecretStore to fetch the ExternalSecret data. + properties: + kind: + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + name: + description: Name of the SecretStore resource + type: string + required: + - name + type: object + target: + default: + creationPolicy: Owner + deletionPolicy: Retain + description: |- + ExternalSecretTarget defines the Kubernetes Secret to be created + There can be only one target per ExternalSecret. + properties: + creationPolicy: + default: Owner + description: |- + CreationPolicy defines rules on how to create the resulting Secret + Defaults to 'Owner' + enum: + - Owner + - Orphan + - Merge + - None + type: string + deletionPolicy: + default: Retain + description: |- + DeletionPolicy defines rules on how to delete the resulting Secret + Defaults to 'Retain' + enum: + - Delete + - Merge + - Retain + type: string + immutable: + description: Immutable defines if the final secret will be immutable + type: boolean + name: + description: |- + Name defines the name of the Secret resource to be managed + This field is immutable + Defaults to the .metadata.name of the ExternalSecret resource + type: string + template: + description: Template defines a blueprint for the created Secret resource. + properties: + data: + additionalProperties: + type: string + type: object + engineVersion: + default: v2 + description: |- + EngineVersion specifies the template engine version + that should be used to compile/execute the + template specified in .data and .templateFrom[]. + enum: + - v1 + - v2 + type: string + mergePolicy: + default: Replace + enum: + - Replace + - Merge + type: string + metadata: + description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + templateFrom: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + literal: + type: string + secret: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + target: + default: Data + enum: + - Data + - Annotations + - Labels + type: string + type: object + type: array + type: + type: string + type: object + type: object + type: object + status: + properties: + binding: + description: Binding represents a servicebinding.io Provisioned Service reference to the secret + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + refreshTime: + description: |- + refreshTime is the time and date the external secret was fetched and + the target secret updated + format: date-time + nullable: true + type: string + syncedResourceVersion: + description: SyncedResourceVersion keeps track of the last synced version + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/fake.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/fake.yaml new file mode 100644 index 0000000000..df6694792d --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/fake.yaml @@ -0,0 +1,86 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: fakes.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - fake + kind: Fake + listKind: FakeList + plural: fakes + shortNames: + - fake + singular: fake + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Fake generator is used for testing. It lets you define + a static set of credentials that is always returned. + 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: FakeSpec contains the static data. + properties: + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters VDS based on this property + type: string + data: + additionalProperties: + type: string + description: |- + Data defines the static data returned + by this generator. + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/gcraccesstoken.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/gcraccesstoken.yaml new file mode 100644 index 0000000000..5be5f4efad --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/gcraccesstoken.yaml @@ -0,0 +1,138 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: gcraccesstokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - gcraccesstoken + kind: GCRAccessToken + listKind: GCRAccessTokenList + plural: gcraccesstokens + shortNames: + - gcraccesstoken + singular: gcraccesstoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + GCRAccessToken generates an GCP access token + that can be used to authenticate with GCR. + 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: + properties: + auth: + description: Auth defines the means for authenticating with GCP + properties: + secretRef: + properties: + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + workloadIdentity: + properties: + clusterLocation: + type: string + clusterName: + type: string + clusterProjectID: + type: string + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - clusterLocation + - clusterName + - serviceAccountRef + type: object + type: object + projectID: + description: ProjectID defines which project to use to authenticate with + type: string + required: + - auth + - projectID + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/githubaccesstoken.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/githubaccesstoken.yaml new file mode 100644 index 0000000000..87fde6889f --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/githubaccesstoken.yaml @@ -0,0 +1,112 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: githubaccesstokens.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - githubaccesstoken + kind: GithubAccessToken + listKind: GithubAccessTokenList + plural: githubaccesstokens + shortNames: + - githubaccesstoken + singular: githubaccesstoken + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: GithubAccessToken generates ghs_ accessToken + 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: + properties: + appID: + type: string + auth: + description: Auth configures how ESO authenticates with a Github instance. + properties: + privateKey: + properties: + secretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - secretRef + type: object + required: + - privateKey + type: object + installID: + type: string + url: + description: URL configures the Github instance URL. Defaults to https://github.com/. + type: string + required: + - appID + - auth + - installID + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/password.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/password.yaml new file mode 100644 index 0000000000..4042bcc50e --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/password.yaml @@ -0,0 +1,108 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: passwords.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - password + kind: Password + listKind: PasswordList + plural: passwords + shortNames: + - password + singular: password + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Password generates a random password based on the + configuration parameters in spec. + You can specify the length, characterset and other attributes. + 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: PasswordSpec controls the behavior of the password generator. + properties: + allowRepeat: + default: false + description: set AllowRepeat to true to allow repeating characters. + type: boolean + digits: + description: |- + Digits specifies the number of digits in the generated + password. If omitted it defaults to 25% of the length of the password + type: integer + length: + default: 24 + description: |- + Length of the password to be generated. + Defaults to 24 + type: integer + noUpper: + default: false + description: Set NoUpper to disable uppercase characters + type: boolean + symbolCharacters: + description: |- + SymbolCharacters specifies the special characters that should be used + in the generated password. + type: string + symbols: + description: |- + Symbols specifies the number of symbol characters in the generated + password. If omitted it defaults to 25% of the length of the password + type: integer + required: + - allowRepeat + - length + - noUpper + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/pushsecret.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/pushsecret.yaml new file mode 100644 index 0000000000..7de0c017fd --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/pushsecret.yaml @@ -0,0 +1,386 @@ +{{- if and (.Values.installCRDs) (.Values.crds.createPushSecret) }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + name: pushsecrets.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - pushsecrets + kind: PushSecret + listKind: PushSecretList + plural: pushsecrets + singular: pushsecret + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + 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: PushSecretSpec configures the behavior of the PushSecret. + properties: + data: + description: Secret Data that should be pushed to providers + items: + properties: + conversionStrategy: + default: None + description: Used to define a conversion Strategy for the secret keys + enum: + - None + - ReverseUnicode + type: string + match: + description: Match a given Secret Key to be pushed to the provider. + properties: + remoteRef: + description: Remote Refs to push to providers. + properties: + property: + description: Name of the property in the resulting secret + type: string + remoteKey: + description: Name of the resulting provider secret. + type: string + required: + - remoteKey + type: object + secretKey: + description: Secret Key to be pushed + type: string + required: + - remoteRef + type: object + metadata: + description: |- + Metadata is metadata attached to the secret. + The structure of metadata is provider specific, please look it up in the provider documentation. + x-kubernetes-preserve-unknown-fields: true + required: + - match + type: object + type: array + deletionPolicy: + default: None + description: 'Deletion Policy to handle Secrets in the provider. Possible Values: "Delete/None". Defaults to "None".' + enum: + - Delete + - None + type: string + refreshInterval: + description: The Interval to which External Secrets will try to push a secret definition + type: string + secretStoreRefs: + items: + properties: + kind: + default: SecretStore + description: |- + Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + Defaults to `SecretStore` + type: string + labelSelector: + description: Optionally, sync to secret stores with label selector + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: Optionally, sync to the SecretStore of the given name + type: string + type: object + type: array + selector: + description: The Secret Selector (k8s source) for the Push Secret + properties: + secret: + description: Select a Secret to Push. + properties: + name: + description: Name of the Secret. The Secret must exist in the same namespace as the PushSecret manifest. + type: string + required: + - name + type: object + required: + - secret + type: object + template: + description: Template defines a blueprint for the created Secret resource. + properties: + data: + additionalProperties: + type: string + type: object + engineVersion: + default: v2 + description: |- + EngineVersion specifies the template engine version + that should be used to compile/execute the + template specified in .data and .templateFrom[]. + enum: + - v1 + - v2 + type: string + mergePolicy: + default: Replace + enum: + - Replace + - Merge + type: string + metadata: + description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + templateFrom: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + literal: + type: string + secret: + properties: + items: + items: + properties: + key: + type: string + templateAs: + default: Values + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + type: string + required: + - items + - name + type: object + target: + default: Data + enum: + - Data + - Annotations + - Labels + type: string + type: object + type: array + type: + type: string + type: object + updatePolicy: + default: Replace + description: 'UpdatePolicy to handle Secrets in the provider. Possible Values: "Replace/IfNotExists". Defaults to "Replace".' + enum: + - Replace + - IfNotExists + type: string + required: + - secretStoreRefs + - selector + type: object + status: + description: PushSecretStatus indicates the history of the status of PushSecret. + properties: + conditions: + items: + description: PushSecretStatusCondition indicates the status of the PushSecret. + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + description: PushSecretConditionType indicates the condition of the PushSecret. + type: string + required: + - status + - type + type: object + type: array + refreshTime: + description: |- + refreshTime is the time and date the external secret was fetched and + the target secret updated + format: date-time + nullable: true + type: string + syncedPushSecrets: + additionalProperties: + additionalProperties: + properties: + conversionStrategy: + default: None + description: Used to define a conversion Strategy for the secret keys + enum: + - None + - ReverseUnicode + type: string + match: + description: Match a given Secret Key to be pushed to the provider. + properties: + remoteRef: + description: Remote Refs to push to providers. + properties: + property: + description: Name of the property in the resulting secret + type: string + remoteKey: + description: Name of the resulting provider secret. + type: string + required: + - remoteKey + type: object + secretKey: + description: Secret Key to be pushed + type: string + required: + - remoteRef + type: object + metadata: + description: |- + Metadata is metadata attached to the secret. + The structure of metadata is provider specific, please look it up in the provider documentation. + x-kubernetes-preserve-unknown-fields: true + required: + - match + type: object + type: object + description: |- + Synced PushSecrets, including secrets that already exist in provider. + Matches secret stores to PushSecretData that was stored to that secret store. + type: object + syncedResourceVersion: + description: SyncedResourceVersion keeps track of the last synced version. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/secretstore.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/secretstore.yaml new file mode 100644 index 0000000000..6b2fe855db --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/secretstore.yaml @@ -0,0 +1,4601 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: secretstores.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - externalsecrets + kind: SecretStore + listKind: SecretStoreList + plural: secretstores + shortNames: + - ss + singular: secretstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + deprecated: true + name: v1alpha1 + schema: + openAPIV3Schema: + description: SecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields. + 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: SecretStoreSpec defines the desired state of SecretStore. + properties: + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters ES based on this property + type: string + provider: + description: Used to configure the provider. Only one provider may be set + maxProperties: 1 + minProperties: 1 + properties: + akeyless: + description: Akeyless configures this store to sync secrets using Akeyless Vault provider + properties: + akeylessGWApiURL: + description: Akeyless GW API Url from which the secrets to be fetched from. + type: string + authSecretRef: + description: Auth configures how the operator authenticates with Akeyless. + properties: + kubernetesAuth: + description: |- + Kubernetes authenticates with Akeyless by passing the ServiceAccount + token stored in the named Secret resource. + properties: + accessID: + description: the Akeyless Kubernetes auth-method access-id + type: string + k8sConfName: + description: Kubernetes-auth configuration name in Akeyless-Gateway + type: string + secretRef: + description: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Akeyless. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Akeyless. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - accessID + - k8sConfName + type: object + secretRef: + description: |- + Reference to a Secret that contains the details + to authenticate with Akeyless. + properties: + accessID: + description: The SecretAccessID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessType: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessTypeParam: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + caBundle: + description: |- + PEM/base64 encoded CA bundle used to validate Akeyless Gateway certificate. Only used + if the AkeylessGWApiURL URL is using HTTPS protocol. If not set the system root certificates + are used to validate the TLS connection. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Akeyless Gateway certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + required: + - akeylessGWApiURL + - authSecretRef + type: object + alibaba: + description: Alibaba configures this store to sync secrets using Alibaba Cloud provider + properties: + auth: + description: AlibabaAuth contains a secretRef for credentials. + properties: + rrsa: + description: Authenticate against Alibaba using RRSA. + properties: + oidcProviderArn: + type: string + oidcTokenFilePath: + type: string + roleArn: + type: string + sessionName: + type: string + required: + - oidcProviderArn + - oidcTokenFilePath + - roleArn + - sessionName + type: object + secretRef: + description: AlibabaAuthSecretRef holds secret references for Alibaba credentials. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessKeySecretSecretRef: + description: The AccessKeySecret is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - accessKeyIDSecretRef + - accessKeySecretSecretRef + type: object + type: object + regionID: + description: Alibaba Region to be used for the provider + type: string + required: + - auth + - regionID + type: object + aws: + description: AWS configures this store to sync secrets using AWS Secret Manager provider + properties: + auth: + description: |- + Auth defines the information necessary to authenticate against AWS + if not set aws sdk will infer credentials from your environment + see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + properties: + jwt: + description: Authenticate against AWS using service account tokens. + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + secretRef: + description: |- + AWSAuthSecretRef holds secret references for AWS credentials + both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + region: + description: AWS Region to be used for the provider + type: string + role: + description: Role is a Role ARN which the SecretManager provider will assume + type: string + service: + description: Service defines which service should be used to fetch the secrets + enum: + - SecretsManager + - ParameterStore + type: string + required: + - region + - service + type: object + azurekv: + description: AzureKV configures this store to sync secrets using Azure Key Vault provider + properties: + authSecretRef: + description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. + properties: + clientId: + description: The Azure clientId of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: The Azure ClientSecret of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + authType: + default: ServicePrincipal + description: |- + Auth type defines how to authenticate to the keyvault service. + Valid values are: + - "ServicePrincipal" (default): Using a service principal (tenantId, clientId, clientSecret) + - "ManagedIdentity": Using Managed Identity assigned to the pod (see aad-pod-identity) + enum: + - ServicePrincipal + - ManagedIdentity + - WorkloadIdentity + type: string + identityId: + description: If multiple Managed Identity is assigned to the pod, you can select the one to be used + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + tenantId: + description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. + type: string + vaultUrl: + description: Vault Url from which the secrets to be fetched from. + type: string + required: + - vaultUrl + type: object + fake: + description: Fake configures a store with static key/value pairs + properties: + data: + items: + properties: + key: + type: string + value: + type: string + valueMap: + additionalProperties: + type: string + type: object + version: + type: string + required: + - key + type: object + type: array + required: + - data + type: object + gcpsm: + description: GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider + properties: + auth: + description: Auth defines the information necessary to authenticate against GCP + properties: + secretRef: + properties: + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + workloadIdentity: + properties: + clusterLocation: + type: string + clusterName: + type: string + clusterProjectID: + type: string + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - clusterLocation + - clusterName + - serviceAccountRef + type: object + type: object + projectID: + description: ProjectID project where secret is located + type: string + type: object + gitlab: + description: GitLab configures this store to sync secrets using GitLab Variables provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a GitLab instance. + properties: + SecretRef: + properties: + accessToken: + description: AccessToken is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - SecretRef + type: object + projectID: + description: ProjectID specifies a project where secrets are located. + type: string + url: + description: URL configures the GitLab instance URL. Defaults to https://gitlab.com/. + type: string + required: + - auth + type: object + ibm: + description: IBM configures this store to sync secrets using IBM Cloud provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the IBM secrets manager. + properties: + secretRef: + properties: + secretApiKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + serviceUrl: + description: ServiceURL is the Endpoint URL that is specific to the Secrets Manager service instance + type: string + required: + - auth + type: object + kubernetes: + description: Kubernetes configures this store to sync secrets using a Kubernetes cluster provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Kubernetes instance. + maxProperties: 1 + minProperties: 1 + properties: + cert: + description: has both clientCert and clientKey as secretKeySelector + properties: + clientCert: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientKey: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + serviceAccount: + description: points to a service account that should be used for authentication + properties: + serviceAccount: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + token: + description: use static token to authenticate with + properties: + bearerToken: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + remoteNamespace: + default: default + description: Remote namespace to fetch the secrets from + type: string + server: + description: configures the Kubernetes server Address. + properties: + caBundle: + description: CABundle is a base64-encoded CA certificate + format: byte + type: string + caProvider: + description: 'see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + default: kubernetes.default + description: configures the Kubernetes server Address. + type: string + type: object + required: + - auth + type: object + oracle: + description: Oracle configures this store to sync secrets using Oracle Vault provider + properties: + auth: + description: |- + Auth configures how secret-manager authenticates with the Oracle Vault. + If empty, instance principal is used. Optionally, the authenticating principal type + and/or user data may be supplied for the use of workload identity and user principal. + properties: + secretRef: + description: SecretRef to pass through sensitive information. + properties: + fingerprint: + description: Fingerprint is the fingerprint of the API private key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privatekey: + description: PrivateKey is the user's API Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - fingerprint + - privatekey + type: object + tenancy: + description: Tenancy is the tenancy OCID where user is located. + type: string + user: + description: User is an access OCID specific to the account. + type: string + required: + - secretRef + - tenancy + - user + type: object + compartment: + description: |- + Compartment is the vault compartment OCID. + Required for PushSecret + type: string + encryptionKey: + description: |- + EncryptionKey is the OCID of the encryption key within the vault. + Required for PushSecret + type: string + principalType: + description: |- + The type of principal to use for authentication. If left blank, the Auth struct will + determine the principal type. This optional field must be specified if using + workload identity. + enum: + - "" + - UserPrincipal + - InstancePrincipal + - Workload + type: string + region: + description: Region is the region where vault is located. + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + vault: + description: Vault is the vault's OCID of the specific vault where secret is located. + type: string + required: + - region + - vault + type: object + passworddepot: + description: Configures a store to sync secrets with a Password Depot instance. + properties: + auth: + description: Auth configures how secret-manager authenticates with a Password Depot instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + database: + description: Database to use as source + type: string + host: + description: URL configures the Password Depot instance URL. + type: string + required: + - auth + - database + - host + type: object + vault: + description: Vault configures this store to sync secrets using Hashi provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + default: approle + 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. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + - roleId + - secretRef + type: object + cert: + description: |- + Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate + Cert authentication method + properties: + clientCert: + description: |- + ClientCert is a certificate to authenticate using the Cert Vault + authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretRef: + description: |- + SecretRef to a key in a Secret resource containing client private key to + authenticate with Vault using the Cert authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + jwt: + description: |- + Jwt authenticates with Vault by passing role and JWT token using the + JWT/OIDC authentication method + properties: + kubernetesServiceAccountToken: + description: |- + Optional ServiceAccountToken specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Optional audiences field that will be used to request a temporary Kubernetes service + account token for the service account referenced by `serviceAccountRef`. + Defaults to a single audience `vault` it not specified. + items: + type: string + type: array + expirationSeconds: + description: |- + Optional expiration time in seconds that will be used to request a temporary + Kubernetes service account token for the service account referenced by + `serviceAccountRef`. + Defaults to 10 minutes. + format: int64 + type: integer + serviceAccountRef: + description: Service account field containing the name of a kubernetes ServiceAccount. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + path: + default: jwt + description: |- + Path where the JWT authentication backend is mounted + in Vault, e.g: "jwt" + type: string + role: + description: |- + Role is a JWT role to authenticate using the JWT/OIDC Vault + authentication method + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Vault using the JWT/OIDC authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + default: kubernetes + description: |- + Path where the Kubernetes authentication backend is mounted in Vault, e.g: + "kubernetes" + 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: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Vault. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - mountPath + - role + type: object + ldap: + description: |- + Ldap authenticates with Vault by passing username/password pair using + the LDAP authentication method + properties: + path: + default: ldap + description: |- + Path where the LDAP authentication backend is mounted + in Vault, e.g: "ldap" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the LDAP + user used to authenticate with Vault using the LDAP authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a LDAP user name used to authenticate using the LDAP Vault + authentication method + type: string + required: + - path + - username + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caBundle: + description: |- + PEM encoded CA bundle 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Vault server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + forwardInconsistent: + description: |- + ForwardInconsistent tells Vault to forward read-after-write requests to the Vault + leader instead of simply retrying within a loop. This can increase performance if + the option is enabled serverside. + https://www.vaultproject.io/docs/configuration/replication#allow_forwarding_via_header + type: boolean + 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 KV backend endpoint, e.g: + "secret". The v2 KV secret engine version specific "/data" path suffix + for fetching secrets from Vault is optional and will be appended + if not present in specified path. + type: string + readYourWrites: + description: |- + ReadYourWrites ensures isolated read-after-write semantics by + providing discovered cluster replication states in each request. + More information about eventual consistency in Vault can be found here + https://www.vaultproject.io/docs/enterprise/consistency + type: boolean + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + version: + default: v2 + description: |- + Version is the Vault KV secret engine version. This can be either "v1" or + "v2". Version defaults to "v2". + enum: + - v1 + - v2 + type: string + required: + - auth + - server + type: object + webhook: + description: Webhook configures this store to sync secrets using a generic templated webhook + properties: + body: + description: Body + type: string + caBundle: + description: |- + PEM encoded CA bundle used to validate webhook 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate webhook server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + headers: + additionalProperties: + type: string + description: Headers + type: object + method: + description: Webhook Method + type: string + result: + description: Result formatting + properties: + jsonPath: + description: Json path of return value + type: string + type: object + secrets: + description: |- + Secrets to fill in templates + These secrets will be passed to the templating function as key value pairs under the given name + items: + properties: + name: + description: Name of this secret in templates + type: string + secretRef: + description: Secret ref to fill in credentials + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - name + - secretRef + type: object + type: array + timeout: + description: Timeout + type: string + url: + description: Webhook url to call + type: string + required: + - result + - url + type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets using Yandex Lockbox provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + type: object + retrySettings: + description: Used to configure http retries if failed + properties: + maxRetries: + format: int32 + type: integer + retryInterval: + type: string + type: object + required: + - provider + type: object + status: + description: SecretStoreStatus defines the observed state of the SecretStore. + properties: + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + - jsonPath: .status.capabilities + name: Capabilities + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: SecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields. + 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: SecretStoreSpec defines the desired state of SecretStore. + properties: + conditions: + description: Used to constraint a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore + items: + description: |- + ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in + for a ClusterSecretStore instance. + properties: + namespaceRegexes: + description: Choose namespaces by using regex matching + items: + type: string + type: array + namespaceSelector: + description: Choose namespace using a labelSelector + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: Choose namespaces by name + items: + type: string + type: array + type: object + type: array + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters ES based on this property + type: string + provider: + description: Used to configure the provider. Only one provider may be set + maxProperties: 1 + minProperties: 1 + properties: + akeyless: + description: Akeyless configures this store to sync secrets using Akeyless Vault provider + properties: + akeylessGWApiURL: + description: Akeyless GW API Url from which the secrets to be fetched from. + type: string + authSecretRef: + description: Auth configures how the operator authenticates with Akeyless. + properties: + kubernetesAuth: + description: |- + Kubernetes authenticates with Akeyless by passing the ServiceAccount + token stored in the named Secret resource. + properties: + accessID: + description: the Akeyless Kubernetes auth-method access-id + type: string + k8sConfName: + description: Kubernetes-auth configuration name in Akeyless-Gateway + type: string + secretRef: + description: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Akeyless. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Akeyless. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - accessID + - k8sConfName + type: object + secretRef: + description: |- + Reference to a Secret that contains the details + to authenticate with Akeyless. + properties: + accessID: + description: The SecretAccessID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessType: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessTypeParam: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + caBundle: + description: |- + PEM/base64 encoded CA bundle used to validate Akeyless Gateway certificate. Only used + if the AkeylessGWApiURL URL is using HTTPS protocol. If not set the system root certificates + are used to validate the TLS connection. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Akeyless Gateway certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + required: + - akeylessGWApiURL + - authSecretRef + type: object + alibaba: + description: Alibaba configures this store to sync secrets using Alibaba Cloud provider + properties: + auth: + description: AlibabaAuth contains a secretRef for credentials. + properties: + rrsa: + description: Authenticate against Alibaba using RRSA. + properties: + oidcProviderArn: + type: string + oidcTokenFilePath: + type: string + roleArn: + type: string + sessionName: + type: string + required: + - oidcProviderArn + - oidcTokenFilePath + - roleArn + - sessionName + type: object + secretRef: + description: AlibabaAuthSecretRef holds secret references for Alibaba credentials. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + accessKeySecretSecretRef: + description: The AccessKeySecret is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - accessKeyIDSecretRef + - accessKeySecretSecretRef + type: object + type: object + regionID: + description: Alibaba Region to be used for the provider + type: string + required: + - auth + - regionID + type: object + aws: + description: AWS configures this store to sync secrets using AWS Secret Manager provider + properties: + additionalRoles: + description: AdditionalRoles is a chained list of Role ARNs which the provider will sequentially assume before assuming the Role + items: + type: string + type: array + auth: + description: |- + Auth defines the information necessary to authenticate against AWS + if not set aws sdk will infer credentials from your environment + see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials + properties: + jwt: + description: Authenticate against AWS using service account tokens. + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + secretRef: + description: |- + AWSAuthSecretRef holds secret references for AWS credentials + both AccessKeyID and SecretAccessKey must be defined in order to properly authenticate. + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + externalID: + description: AWS External ID set on assumed IAM roles + type: string + prefix: + description: Prefix adds a prefix to all retrieved values. + type: string + region: + description: AWS Region to be used for the provider + type: string + role: + description: Role is a Role ARN which the provider will assume + type: string + secretsManager: + description: SecretsManager defines how the provider behaves when interacting with AWS SecretsManager + properties: + forceDeleteWithoutRecovery: + description: |- + Specifies whether to delete the secret without any recovery window. You + can't use both this parameter and RecoveryWindowInDays in the same call. + If you don't use either, then by default Secrets Manager uses a 30 day + recovery window. + see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-ForceDeleteWithoutRecovery + type: boolean + recoveryWindowInDays: + description: |- + The number of days from 7 to 30 that Secrets Manager waits before + permanently deleting the secret. You can't use both this parameter and + ForceDeleteWithoutRecovery in the same call. If you don't use either, + then by default Secrets Manager uses a 30 day recovery window. + see: https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html#SecretsManager-DeleteSecret-request-RecoveryWindowInDays + format: int64 + type: integer + type: object + service: + description: Service defines which service should be used to fetch the secrets + enum: + - SecretsManager + - ParameterStore + type: string + sessionTags: + description: AWS STS assume role session tags + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + transitiveTagKeys: + description: AWS STS assume role transitive session tags. Required when multiple rules are used with the provider + items: + type: string + type: array + required: + - region + - service + type: object + azurekv: + description: AzureKV configures this store to sync secrets using Azure Key Vault provider + properties: + authSecretRef: + description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity. + properties: + clientCertificate: + description: The Azure ClientCertificate of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientId: + description: The Azure clientId of the service principle or managed identity used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: The Azure ClientSecret of the service principle used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + tenantId: + description: The Azure tenantId of the managed identity used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + authType: + default: ServicePrincipal + description: |- + Auth type defines how to authenticate to the keyvault service. + Valid values are: + - "ServicePrincipal" (default): Using a service principal (tenantId, clientId, clientSecret) + - "ManagedIdentity": Using Managed Identity assigned to the pod (see aad-pod-identity) + enum: + - ServicePrincipal + - ManagedIdentity + - WorkloadIdentity + type: string + environmentType: + default: PublicCloud + description: |- + EnvironmentType specifies the Azure cloud environment endpoints to use for + connecting and authenticating with Azure. By default it points to the public cloud AAD endpoint. + The following endpoints are available, also see here: https://github.com/Azure/go-autorest/blob/main/autorest/azure/environments.go#L152 + PublicCloud, USGovernmentCloud, ChinaCloud, GermanCloud + enum: + - PublicCloud + - USGovernmentCloud + - ChinaCloud + - GermanCloud + type: string + identityId: + description: If multiple Managed Identity is assigned to the pod, you can select the one to be used + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + tenantId: + description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity. + type: string + vaultUrl: + description: Vault Url from which the secrets to be fetched from. + type: string + required: + - vaultUrl + type: object + beyondtrust: + description: Beyondtrust configures this store to sync secrets using Password Safe provider. + properties: + auth: + description: Auth configures how the operator authenticates with Beyondtrust. + properties: + certificate: + description: Content of the certificate (cert.pem) for use when authenticating with an OAuth client Id using a Client Certificate. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + certificateKey: + description: Certificate private key (key.pem). For use when authenticating with an OAuth client Id + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientId: + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientSecret: + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - clientId + - clientSecret + type: object + server: + description: Auth configures how API server works. + properties: + apiUrl: + type: string + clientTimeOutSeconds: + description: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. Defaults to 45 seconds. + type: integer + retrievalType: + description: The secret retrieval type. SECRET = Secrets Safe (credential, text, file). MANAGED_ACCOUNT = Password Safe account associated with a system. + type: string + separator: + description: A character that separates the folder names. + type: string + verifyCA: + type: boolean + required: + - apiUrl + - verifyCA + type: object + required: + - auth + - server + type: object + bitwardensecretsmanager: + description: BitwardenSecretsManager configures this store to sync secrets using BitwardenSecretsManager provider + properties: + apiURL: + type: string + auth: + description: |- + Auth configures how secret-manager authenticates with a bitwarden machine account instance. + Make sure that the token being used has permissions on the given secret. + properties: + secretRef: + description: BitwardenSecretsManagerSecretRef contains the credential ref to the bitwarden instance. + properties: + credentials: + description: AccessToken used for the bitwarden instance. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - credentials + type: object + required: + - secretRef + type: object + bitwardenServerSDKURL: + type: string + caBundle: + description: |- + Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack + can be performed. + type: string + caProvider: + description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + identityURL: + type: string + organizationID: + description: OrganizationID determines which organization this secret store manages. + type: string + projectID: + description: ProjectID determines which project this secret store manages. + type: string + required: + - auth + - organizationID + - projectID + type: object + chef: + description: Chef configures this store to sync secrets with chef server + properties: + auth: + description: Auth defines the information necessary to authenticate against chef Server + properties: + secretRef: + description: ChefAuthSecretRef holds secret references for chef server login credentials. + properties: + privateKeySecretRef: + description: SecretKey is the Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - privateKeySecretRef + type: object + required: + - secretRef + type: object + serverUrl: + description: ServerURL is the chef server URL used to connect to. If using orgs you should include your org in the url and terminate the url with a "/" + type: string + username: + description: UserName should be the user ID on the chef server + type: string + required: + - auth + - serverUrl + - username + type: object + conjur: + description: Conjur configures this store to sync secrets using conjur provider + properties: + auth: + properties: + apikey: + properties: + account: + type: string + apiKeyRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + userRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - account + - apiKeyRef + - userRef + type: object + jwt: + properties: + account: + type: string + hostId: + description: |- + Optional HostID for JWT authentication. This may be used depending + on how the Conjur JWT authenticator policy is configured. + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Conjur using the JWT authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional ServiceAccountRef specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + serviceID: + description: The conjur authn jwt webservice id + type: string + required: + - account + - serviceID + type: object + type: object + caBundle: + type: string + caProvider: + description: |- + Used to provide custom certificate authority (CA) certificates + for a secret store. The CAProvider points to a Secret or ConfigMap resource + that contains a PEM-encoded certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + type: string + required: + - auth + - url + type: object + delinea: + description: |- + Delinea DevOps Secrets Vault + https://docs.delinea.com/online-help/products/devops-secrets-vault/current + properties: + clientId: + description: ClientID is the non-secret part of the credential. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + clientSecret: + description: ClientSecret is the secret part of the credential. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + tenant: + description: Tenant is the chosen hostname / site name. + type: string + tld: + description: |- + TLD is based on the server location that was chosen during provisioning. + If unset, defaults to "com". + type: string + urlTemplate: + description: |- + URLTemplate + If unset, defaults to "https://%s.secretsvaultcloud.%s/v1/%s%s". + type: string + required: + - clientId + - clientSecret + - tenant + type: object + device42: + description: Device42 configures this store to sync secrets using the Device42 provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Device42 instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + host: + description: URL configures the Device42 instance URL. + type: string + required: + - auth + - host + type: object + doppler: + description: Doppler configures this store to sync secrets using the Doppler provider + properties: + auth: + description: Auth configures how the Operator authenticates with the Doppler API + properties: + secretRef: + properties: + dopplerToken: + description: |- + The DopplerToken is used for authentication. + See https://docs.doppler.com/reference/api#authentication for auth token types. + The Key attribute defaults to dopplerToken if not specified. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - dopplerToken + type: object + required: + - secretRef + type: object + config: + description: Doppler config (required if not using a Service Token) + type: string + format: + description: Format enables the downloading of secrets as a file (string) + enum: + - json + - dotnet-json + - env + - yaml + - docker + type: string + nameTransformer: + description: Environment variable compatible name transforms that change secret names to a different format + enum: + - upper-camel + - camel + - lower-snake + - tf-var + - dotnet-env + - lower-kebab + type: string + project: + description: Doppler project (required if not using a Service Token) + type: string + required: + - auth + type: object + fake: + description: Fake configures a store with static key/value pairs + properties: + data: + items: + properties: + key: + type: string + value: + type: string + valueMap: + additionalProperties: + type: string + description: 'Deprecated: ValueMap is deprecated and is intended to be removed in the future, use the `value` field instead.' + type: object + version: + type: string + required: + - key + type: object + type: array + required: + - data + type: object + fortanix: + description: Fortanix configures this store to sync secrets using the Fortanix provider + properties: + apiKey: + description: APIKey is the API token to access SDKMS Applications. + properties: + secretRef: + description: SecretRef is a reference to a secret containing the SDKMS API Key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + apiUrl: + description: APIURL is the URL of SDKMS API. Defaults to `sdkms.fortanix.com`. + type: string + type: object + gcpsm: + description: GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider + properties: + auth: + description: Auth defines the information necessary to authenticate against GCP + properties: + secretRef: + properties: + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + workloadIdentity: + properties: + clusterLocation: + type: string + clusterName: + type: string + clusterProjectID: + type: string + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - clusterLocation + - clusterName + - serviceAccountRef + type: object + type: object + location: + description: Location optionally defines a location for a secret + type: string + projectID: + description: ProjectID project where secret is located + type: string + type: object + gitlab: + description: GitLab configures this store to sync secrets using GitLab Variables provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a GitLab instance. + properties: + SecretRef: + properties: + accessToken: + description: AccessToken is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - SecretRef + type: object + environment: + description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments) + type: string + groupIDs: + description: GroupIDs specify, which gitlab groups to pull secrets from. Group secrets are read from left to right followed by the project variables. + items: + type: string + type: array + inheritFromGroups: + description: InheritFromGroups specifies whether parent groups should be discovered and checked for secrets. + type: boolean + projectID: + description: ProjectID specifies a project where secrets are located. + type: string + url: + description: URL configures the GitLab instance URL. Defaults to https://gitlab.com/. + type: string + required: + - auth + type: object + ibm: + description: IBM configures this store to sync secrets using IBM Cloud provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the IBM secrets manager. + maxProperties: 1 + minProperties: 1 + properties: + containerAuth: + description: IBM Container-based auth with IAM Trusted Profile. + properties: + iamEndpoint: + type: string + profile: + description: the IBM Trusted Profile + type: string + tokenLocation: + description: Location the token is mounted on the pod + type: string + required: + - profile + type: object + secretRef: + properties: + secretApiKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + serviceUrl: + description: ServiceURL is the Endpoint URL that is specific to the Secrets Manager service instance + type: string + required: + - auth + type: object + infisical: + description: Infisical configures this store to sync secrets using the Infisical provider + properties: + auth: + description: Auth configures how the Operator authenticates with the Infisical API + properties: + universalAuthCredentials: + properties: + clientId: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientSecret: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - clientId + - clientSecret + type: object + type: object + hostAPI: + default: https://app.infisical.com/api + type: string + secretsScope: + properties: + environmentSlug: + type: string + projectSlug: + type: string + secretsPath: + default: / + type: string + required: + - environmentSlug + - projectSlug + type: object + required: + - auth + - secretsScope + type: object + keepersecurity: + description: KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider + properties: + authRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + folderID: + type: string + required: + - authRef + - folderID + type: object + kubernetes: + description: Kubernetes configures this store to sync secrets using a Kubernetes cluster provider + properties: + auth: + description: Auth configures how secret-manager authenticates with a Kubernetes instance. + maxProperties: 1 + minProperties: 1 + properties: + cert: + description: has both clientCert and clientKey as secretKeySelector + properties: + clientCert: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + clientKey: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + serviceAccount: + description: points to a service account that should be used for authentication + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + token: + description: use static token to authenticate with + properties: + bearerToken: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + type: object + authRef: + description: A reference to a secret that contains the auth information. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + remoteNamespace: + default: default + description: Remote namespace to fetch the secrets from + type: string + server: + description: configures the Kubernetes server Address. + properties: + caBundle: + description: CABundle is a base64-encoded CA certificate + format: byte + type: string + caProvider: + description: 'see: https://external-secrets.io/v0.4.1/spec/#external-secrets.io/v1alpha1.CAProvider' + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + url: + default: kubernetes.default + description: configures the Kubernetes server Address. + type: string + type: object + type: object + onboardbase: + description: Onboardbase configures this store to sync secrets using the Onboardbase provider + properties: + apiHost: + default: https://public.onboardbase.com/api/v1/ + description: APIHost use this to configure the host url for the API for selfhosted installation, default is https://public.onboardbase.com/api/v1/ + type: string + auth: + description: Auth configures how the Operator authenticates with the Onboardbase API + properties: + apiKeyRef: + description: |- + OnboardbaseAPIKey is the APIKey generated by an admin account. + It is used to recognize and authorize access to a project and environment within onboardbase + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + passcodeRef: + description: OnboardbasePasscode is the passcode attached to the API Key + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - apiKeyRef + - passcodeRef + type: object + environment: + default: development + description: Environment is the name of an environmnent within a project to pull the secrets from + type: string + project: + default: development + description: Project is an onboardbase project that the secrets should be pulled from + type: string + required: + - apiHost + - auth + - environment + - project + type: object + onepassword: + description: OnePassword configures this store to sync secrets using the 1Password Cloud provider + properties: + auth: + description: Auth defines the information necessary to authenticate against OnePassword Connect Server + properties: + secretRef: + description: OnePasswordAuthSecretRef holds secret references for 1Password credentials. + properties: + connectTokenSecretRef: + description: The ConnectToken is used for authentication to a 1Password Connect Server. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - connectTokenSecretRef + type: object + required: + - secretRef + type: object + connectHost: + description: ConnectHost defines the OnePassword Connect Server to connect to + type: string + vaults: + additionalProperties: + type: integer + description: Vaults defines which OnePassword vaults to search in which order + type: object + required: + - auth + - connectHost + - vaults + type: object + oracle: + description: Oracle configures this store to sync secrets using Oracle Vault provider + properties: + auth: + description: |- + Auth configures how secret-manager authenticates with the Oracle Vault. + If empty, use the instance principal, otherwise the user credentials specified in Auth. + properties: + secretRef: + description: SecretRef to pass through sensitive information. + properties: + fingerprint: + description: Fingerprint is the fingerprint of the API private key. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privatekey: + description: PrivateKey is the user's API Signing Key in PEM format, used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - fingerprint + - privatekey + type: object + tenancy: + description: Tenancy is the tenancy OCID where user is located. + type: string + user: + description: User is an access OCID specific to the account. + type: string + required: + - secretRef + - tenancy + - user + type: object + compartment: + description: |- + Compartment is the vault compartment OCID. + Required for PushSecret + type: string + encryptionKey: + description: |- + EncryptionKey is the OCID of the encryption key within the vault. + Required for PushSecret + type: string + principalType: + description: |- + The type of principal to use for authentication. If left blank, the Auth struct will + determine the principal type. This optional field must be specified if using + workload identity. + enum: + - "" + - UserPrincipal + - InstancePrincipal + - Workload + type: string + region: + description: Region is the region where vault is located. + type: string + serviceAccountRef: + description: |- + ServiceAccountRef specified the service account + that should be used when authenticating with WorkloadIdentity. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + vault: + description: Vault is the vault's OCID of the specific vault where secret is located. + type: string + required: + - region + - vault + type: object + passbolt: + properties: + auth: + description: Auth defines the information necessary to authenticate against Passbolt Server + properties: + passwordSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + privateKeySecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - passwordSecretRef + - privateKeySecretRef + type: object + host: + description: Host defines the Passbolt Server to connect to + type: string + required: + - auth + - host + type: object + passworddepot: + description: Configures a store to sync secrets with a Password Depot instance. + properties: + auth: + description: Auth configures how secret-manager authenticates with a Password Depot instance. + properties: + secretRef: + properties: + credentials: + description: Username / Password is used for authentication. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - secretRef + type: object + database: + description: Database to use as source + type: string + host: + description: URL configures the Password Depot instance URL. + type: string + required: + - auth + - database + - host + type: object + pulumi: + description: Pulumi configures this store to sync secrets using the Pulumi provider + properties: + accessToken: + description: AccessToken is the access tokens to sign in to the Pulumi Cloud Console. + properties: + secretRef: + description: SecretRef is a reference to a secret containing the Pulumi API token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + apiUrl: + default: https://api.pulumi.com/api/preview + description: APIURL is the URL of the Pulumi API. + type: string + environment: + description: |- + Environment are YAML documents composed of static key-value pairs, programmatic expressions, + dynamically retrieved values from supported providers including all major clouds, + and other Pulumi ESC environments. + To create a new environment, visit https://www.pulumi.com/docs/esc/environments/ for more information. + type: string + organization: + description: |- + Organization are a space to collaborate on shared projects and stacks. + To create a new organization, visit https://app.pulumi.com/ and click "New Organization". + type: string + required: + - accessToken + - environment + - organization + type: object + scaleway: + description: Scaleway + properties: + accessKey: + description: AccessKey is the non-secret part of the api key. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + apiUrl: + description: APIURL is the url of the api to use. Defaults to https://api.scaleway.com + type: string + projectId: + description: 'ProjectID is the id of your project, which you can find in the console: https://console.scaleway.com/project/settings' + type: string + region: + description: 'Region where your secrets are located: https://developers.scaleway.com/en/quickstart/#region-and-zone' + type: string + secretKey: + description: SecretKey is the non-secret part of the api key. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - accessKey + - projectId + - region + - secretKey + type: object + secretserver: + description: |- + SecretServer configures this store to sync secrets using SecretServer provider + https://docs.delinea.com/online-help/secret-server/start.htm + properties: + password: + description: Password is the secret server account password. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + serverURL: + description: |- + ServerURL + URL to your secret server installation + type: string + username: + description: Username is the secret server account username. + properties: + secretRef: + description: SecretRef references a key in a secret that will be used as value. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + value: + description: Value can be specified directly to set a value without using a secret. + type: string + type: object + required: + - password + - serverURL + - username + type: object + senhasegura: + description: Senhasegura configures this store to sync secrets using senhasegura provider + properties: + auth: + description: Auth defines parameters to authenticate in senhasegura + properties: + clientId: + type: string + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - clientId + - clientSecretSecretRef + type: object + ignoreSslCertificate: + default: false + description: IgnoreSslCertificate defines if SSL certificate must be ignored + type: boolean + module: + description: Module defines which senhasegura module should be used to get secrets + type: string + url: + description: URL of senhasegura + type: string + required: + - auth + - module + - url + type: object + vault: + description: Vault configures this store to sync secrets using Hashi provider + properties: + auth: + description: Auth configures how secret-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + default: approle + 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 + roleRef: + description: |- + Reference to a key in a Secret that contains the App Role ID 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 id. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + 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. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + - secretRef + type: object + cert: + description: |- + Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate + Cert authentication method + properties: + clientCert: + description: |- + ClientCert is a certificate to authenticate using the Cert Vault + authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretRef: + description: |- + SecretRef to a key in a Secret resource containing client private key to + authenticate with Vault using the Cert authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + iam: + description: |- + Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials + AWS IAM authentication method + properties: + externalID: + description: AWS External ID set on assumed IAM roles + type: string + jwt: + description: Specify a service account with IRSA enabled + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + path: + description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"' + type: string + region: + description: AWS region + type: string + role: + description: This is the AWS role to be assumed before talking to vault + type: string + secretRef: + description: Specify credentials in a Secret object + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + vaultAwsIamServerID: + description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws' + type: string + vaultRole: + description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine + type: string + required: + - vaultRole + type: object + jwt: + description: |- + Jwt authenticates with Vault by passing role and JWT token using the + JWT/OIDC authentication method + properties: + kubernetesServiceAccountToken: + description: |- + Optional ServiceAccountToken specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Optional audiences field that will be used to request a temporary Kubernetes service + account token for the service account referenced by `serviceAccountRef`. + Defaults to a single audience `vault` it not specified. + Deprecated: use serviceAccountRef.Audiences instead + items: + type: string + type: array + expirationSeconds: + description: |- + Optional expiration time in seconds that will be used to request a temporary + Kubernetes service account token for the service account referenced by + `serviceAccountRef`. + Deprecated: this will be removed in the future. + Defaults to 10 minutes. + format: int64 + type: integer + serviceAccountRef: + description: Service account field containing the name of a kubernetes ServiceAccount. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + path: + default: jwt + description: |- + Path where the JWT authentication backend is mounted + in Vault, e.g: "jwt" + type: string + role: + description: |- + Role is a JWT role to authenticate using the JWT/OIDC Vault + authentication method + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Vault using the JWT/OIDC authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + default: kubernetes + description: |- + Path where the Kubernetes authentication backend is mounted in Vault, e.g: + "kubernetes" + 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: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Vault. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - mountPath + - role + type: object + ldap: + description: |- + Ldap authenticates with Vault by passing username/password pair using + the LDAP authentication method + properties: + path: + default: ldap + description: |- + Path where the LDAP authentication backend is mounted + in Vault, e.g: "ldap" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the LDAP + user used to authenticate with Vault using the LDAP authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a LDAP user name used to authenticate using the LDAP Vault + authentication method + type: string + required: + - path + - username + type: object + namespace: + description: |- + Name of the vault namespace to authenticate to. This can be different than the namespace your secret is in. + 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 + This will default to Vault.Namespace field if set, or empty otherwise + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + userPass: + description: UserPass authenticates with Vault by passing username/password pair + properties: + path: + default: user + description: |- + Path where the UserPassword authentication backend is mounted + in Vault, e.g: "user" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the + user used to authenticate with Vault using the UserPass authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a user name used to authenticate using the UserPass Vault + authentication method + type: string + required: + - path + - username + type: object + type: object + caBundle: + description: |- + PEM encoded CA bundle 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Vault server certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + forwardInconsistent: + description: |- + ForwardInconsistent tells Vault to forward read-after-write requests to the Vault + leader instead of simply retrying within a loop. This can increase performance if + the option is enabled serverside. + https://www.vaultproject.io/docs/configuration/replication#allow_forwarding_via_header + type: boolean + headers: + additionalProperties: + type: string + description: Headers to be added in Vault request + type: object + 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 KV backend endpoint, e.g: + "secret". The v2 KV secret engine version specific "/data" path suffix + for fetching secrets from Vault is optional and will be appended + if not present in specified path. + type: string + readYourWrites: + description: |- + ReadYourWrites ensures isolated read-after-write semantics by + providing discovered cluster replication states in each request. + More information about eventual consistency in Vault can be found here + https://www.vaultproject.io/docs/enterprise/consistency + type: boolean + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + tls: + description: |- + The configuration used for client side related TLS communication, when the Vault server + requires mutual authentication. Only used if the Server URL is using HTTPS protocol. + This parameter is ignored for plain HTTP protocol connection. + It's worth noting this configuration is different from the "TLS certificates auth method", + which is available under the `auth.cert` section. + properties: + certSecretRef: + description: |- + CertSecretRef is a certificate added to the transport layer + when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.crt'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + keySecretRef: + description: |- + KeySecretRef to a key in a Secret resource containing client private key + added to the transport layer when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.key'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + version: + default: v2 + description: |- + Version is the Vault KV secret engine version. This can be either "v1" or + "v2". Version defaults to "v2". + enum: + - v1 + - v2 + type: string + required: + - auth + - server + type: object + webhook: + description: Webhook configures this store to sync secrets using a generic templated webhook + properties: + body: + description: Body + type: string + caBundle: + description: |- + PEM encoded CA bundle used to validate webhook 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate webhook server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + headers: + additionalProperties: + type: string + description: Headers + type: object + method: + description: Webhook Method + type: string + result: + description: Result formatting + properties: + jsonPath: + description: Json path of return value + type: string + type: object + secrets: + description: |- + Secrets to fill in templates + These secrets will be passed to the templating function as key value pairs under the given name + items: + properties: + name: + description: Name of this secret in templates + type: string + secretRef: + description: Secret ref to fill in credentials + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - name + - secretRef + type: object + type: array + timeout: + description: Timeout + type: string + url: + description: Webhook url to call + type: string + required: + - result + - url + type: object + yandexcertificatemanager: + description: YandexCertificateManager configures this store to sync secrets using Yandex Certificate Manager provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Certificate Manager + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + yandexlockbox: + description: YandexLockbox configures this store to sync secrets using Yandex Lockbox provider + properties: + apiEndpoint: + description: Yandex.Cloud API endpoint (e.g. 'api.cloud.yandex.net:443') + type: string + auth: + description: Auth defines the information necessary to authenticate against Yandex Lockbox + properties: + authorizedKeySecretRef: + description: The authorized key used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + caProvider: + description: The provider for the CA bundle to use to validate Yandex.Cloud server certificate. + properties: + certSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource, + In some instances, `key` is a required field. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + required: + - auth + type: object + type: object + refreshInterval: + description: Used to configure store refresh interval in seconds. Empty or 0 will default to the controller config. + type: integer + retrySettings: + description: Used to configure http retries if failed + properties: + maxRetries: + format: int32 + type: integer + retryInterval: + type: string + type: object + required: + - provider + type: object + status: + description: SecretStoreStatus defines the observed state of the SecretStore. + properties: + capabilities: + description: SecretStoreCapabilities defines the possible operations a SecretStore can do. + type: string + conditions: + items: + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + type: string + required: + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/vaultdynamicsecret.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/vaultdynamicsecret.yaml new file mode 100644 index 0000000000..816c3a5824 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/vaultdynamicsecret.yaml @@ -0,0 +1,707 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: vaultdynamicsecrets.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - vaultdynamicsecret + kind: VaultDynamicSecret + listKind: VaultDynamicSecretList + plural: vaultdynamicsecrets + shortNames: + - vaultdynamicsecret + singular: vaultdynamicsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + 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: + properties: + controller: + description: |- + Used to select the correct ESO controller (think: ingress.ingressClassName) + The ESO controller is instantiated with a specific controller name and filters VDS based on this property + type: string + method: + description: Vault API method to use (GET/POST/other) + type: string + parameters: + description: Parameters to pass to Vault write (for non-GET methods) + x-kubernetes-preserve-unknown-fields: true + path: + description: Vault path to obtain the dynamic secret from + type: string + provider: + description: Vault provider common spec + properties: + auth: + description: Auth configures how secret-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + default: approle + 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 + roleRef: + description: |- + Reference to a key in a Secret that contains the App Role ID 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 id. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + 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. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + - secretRef + type: object + cert: + description: |- + Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate + Cert authentication method + properties: + clientCert: + description: |- + ClientCert is a certificate to authenticate using the Cert Vault + authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretRef: + description: |- + SecretRef to a key in a Secret resource containing client private key to + authenticate with Vault using the Cert authentication method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + iam: + description: |- + Iam authenticates with vault by passing a special AWS request signed with AWS IAM credentials + AWS IAM authentication method + properties: + externalID: + description: AWS External ID set on assumed IAM roles + type: string + jwt: + description: Specify a service account with IRSA enabled + properties: + serviceAccountRef: + description: A reference to a ServiceAccount resource. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + type: object + path: + description: 'Path where the AWS auth method is enabled in Vault, e.g: "aws"' + type: string + region: + description: AWS region + type: string + role: + description: This is the AWS role to be assumed before talking to vault + type: string + secretRef: + description: Specify credentials in a Secret object + properties: + accessKeyIDSecretRef: + description: The AccessKeyID is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + secretAccessKeySecretRef: + description: The SecretAccessKey is used for authentication + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + sessionTokenSecretRef: + description: |- + The SessionToken used for authentication + This must be defined if AccessKeyID and SecretAccessKey are temporary credentials + see: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + vaultAwsIamServerID: + description: 'X-Vault-AWS-IAM-Server-ID is an additional header used by Vault IAM auth method to mitigate against different types of replay attacks. More details here: https://developer.hashicorp.com/vault/docs/auth/aws' + type: string + vaultRole: + description: Vault Role. In vault, a role describes an identity with a set of permissions, groups, or policies you want to attach a user of the secrets engine + type: string + required: + - vaultRole + type: object + jwt: + description: |- + Jwt authenticates with Vault by passing role and JWT token using the + JWT/OIDC authentication method + properties: + kubernetesServiceAccountToken: + description: |- + Optional ServiceAccountToken specifies the Kubernetes service account for which to request + a token for with the `TokenRequest` API. + properties: + audiences: + description: |- + Optional audiences field that will be used to request a temporary Kubernetes service + account token for the service account referenced by `serviceAccountRef`. + Defaults to a single audience `vault` it not specified. + Deprecated: use serviceAccountRef.Audiences instead + items: + type: string + type: array + expirationSeconds: + description: |- + Optional expiration time in seconds that will be used to request a temporary + Kubernetes service account token for the service account referenced by + `serviceAccountRef`. + Deprecated: this will be removed in the future. + Defaults to 10 minutes. + format: int64 + type: integer + serviceAccountRef: + description: Service account field containing the name of a kubernetes ServiceAccount. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + path: + default: jwt + description: |- + Path where the JWT authentication backend is mounted + in Vault, e.g: "jwt" + type: string + role: + description: |- + Role is a JWT role to authenticate using the JWT/OIDC Vault + authentication method + type: string + secretRef: + description: |- + Optional SecretRef that refers to a key in a Secret resource containing JWT token to + authenticate with Vault using the JWT/OIDC authentication method. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + required: + - path + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + default: kubernetes + description: |- + Path where the Kubernetes authentication backend is mounted in Vault, e.g: + "kubernetes" + 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: |- + Optional secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. If a name is specified without a key, + `token` is the default. If one is not specified, the one bound to + the controller will be used. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + serviceAccountRef: + description: |- + Optional service account field containing the name of a kubernetes ServiceAccount. + If the service account is specified, the service account secret token JWT will be used + for authenticating with Vault. If the service account selector is not supplied, + the secretRef will be used instead. + properties: + audiences: + description: |- + Audience specifies the `aud` claim for the service account token + If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity + then this audiences will be appended to the list + items: + type: string + type: array + name: + description: The name of the ServiceAccount resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + required: + - name + type: object + required: + - mountPath + - role + type: object + ldap: + description: |- + Ldap authenticates with Vault by passing username/password pair using + the LDAP authentication method + properties: + path: + default: ldap + description: |- + Path where the LDAP authentication backend is mounted + in Vault, e.g: "ldap" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the LDAP + user used to authenticate with Vault using the LDAP authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a LDAP user name used to authenticate using the LDAP Vault + authentication method + type: string + required: + - path + - username + type: object + namespace: + description: |- + Name of the vault namespace to authenticate to. This can be different than the namespace your secret is in. + 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 + This will default to Vault.Namespace field if set, or empty otherwise + type: string + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + userPass: + description: UserPass authenticates with Vault by passing username/password pair + properties: + path: + default: user + description: |- + Path where the UserPassword authentication backend is mounted + in Vault, e.g: "user" + type: string + secretRef: + description: |- + SecretRef to a key in a Secret resource containing password for the + user used to authenticate with Vault using the UserPass authentication + method + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + username: + description: |- + Username is a user name used to authenticate using the UserPass Vault + authentication method + type: string + required: + - path + - username + type: object + type: object + caBundle: + description: |- + PEM encoded CA bundle 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate Vault server certificate. + properties: + key: + description: The key where the CA certificate can be found in the Secret or ConfigMap. + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: |- + The namespace the Provider type is in. + Can only be defined when used in a ClusterSecretStore. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + forwardInconsistent: + description: |- + ForwardInconsistent tells Vault to forward read-after-write requests to the Vault + leader instead of simply retrying within a loop. This can increase performance if + the option is enabled serverside. + https://www.vaultproject.io/docs/configuration/replication#allow_forwarding_via_header + type: boolean + headers: + additionalProperties: + type: string + description: Headers to be added in Vault request + type: object + 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 KV backend endpoint, e.g: + "secret". The v2 KV secret engine version specific "/data" path suffix + for fetching secrets from Vault is optional and will be appended + if not present in specified path. + type: string + readYourWrites: + description: |- + ReadYourWrites ensures isolated read-after-write semantics by + providing discovered cluster replication states in each request. + More information about eventual consistency in Vault can be found here + https://www.vaultproject.io/docs/enterprise/consistency + type: boolean + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + tls: + description: |- + The configuration used for client side related TLS communication, when the Vault server + requires mutual authentication. Only used if the Server URL is using HTTPS protocol. + This parameter is ignored for plain HTTP protocol connection. + It's worth noting this configuration is different from the "TLS certificates auth method", + which is available under the `auth.cert` section. + properties: + certSecretRef: + description: |- + CertSecretRef is a certificate added to the transport layer + when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.crt'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + keySecretRef: + description: |- + KeySecretRef to a key in a Secret resource containing client private key + added to the transport layer when communicating with the Vault server. + If no key for the Secret is specified, external-secret will default to 'tls.key'. + 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: The name of the Secret resource being referred to. + type: string + namespace: + description: |- + Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults + to the namespace of the referent. + type: string + type: object + type: object + version: + default: v2 + description: |- + Version is the Vault KV secret engine version. This can be either "v1" or + "v2". Version defaults to "v2". + enum: + - v1 + - v2 + type: string + required: + - auth + - server + type: object + resultType: + default: Data + description: |- + Result type defines which data is returned from the generator. + By default it is the "data" section of the Vault API response. + When using e.g. /auth/token/create the "data" section is empty but + the "auth" section contains the generated token. + Please refer to the vault docs regarding the result data structure. + enum: + - Data + - Auth + type: string + required: + - path + - provider + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/crds/webhook.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/crds/webhook.yaml new file mode 100644 index 0000000000..ac6a859ee5 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/crds/webhook.yaml @@ -0,0 +1,157 @@ +{{- if .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + {{- with .Values.crds.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- if and .Values.crds.conversion.enabled .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} + controller-gen.kubebuilder.io/version: v0.16.1 + labels: + external-secrets.io/component: controller + name: webhooks.generators.external-secrets.io +spec: + group: generators.external-secrets.io + names: + categories: + - webhook + kind: Webhook + listKind: WebhookList + plural: webhooks + shortNames: + - webhookl + singular: webhook + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Webhook connects to a third party API server to handle the secrets generation + configuration parameters in spec. + You can specify the server, the token, and additional body parameters. + See documentation for the full API specification for requests and responses. + 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: WebhookSpec controls the behavior of the external generator. Any body parameters should be passed to the server through the parameters field. + properties: + body: + description: Body + type: string + caBundle: + description: |- + PEM encoded CA bundle used to validate webhook 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. + format: byte + type: string + caProvider: + description: The provider for the CA bundle to use to validate webhook server certificate. + properties: + key: + description: The key the value inside of the provider type to use, only used with "Secret" type + type: string + name: + description: The name of the object located at the provider type. + type: string + namespace: + description: The namespace the Provider type is in. + type: string + type: + description: The type of provider to use such as "Secret", or "ConfigMap". + enum: + - Secret + - ConfigMap + type: string + required: + - name + - type + type: object + headers: + additionalProperties: + type: string + description: Headers + type: object + method: + description: Webhook Method + type: string + result: + description: Result formatting + properties: + jsonPath: + description: Json path of return value + type: string + type: object + secrets: + description: |- + Secrets to fill in templates + These secrets will be passed to the templating function as key value pairs under the given name + items: + properties: + name: + description: Name of this secret in templates + type: string + secretRef: + description: Secret ref to fill in credentials + properties: + key: + description: The key where the token is found. + type: string + name: + description: The name of the Secret resource being referred to. + type: string + type: object + required: + - name + - secretRef + type: object + type: array + timeout: + description: Timeout + type: string + url: + description: Webhook url to call + type: string + required: + - result + - url + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- if .Values.crds.conversion.enabled }} + conversion: + strategy: Webhook + webhook: + conversionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ .Release.Namespace | quote }} + path: /convert +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/deployment.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/deployment.yaml new file mode 100644 index 0000000000..75a908e635 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/deployment.yaml @@ -0,0 +1,146 @@ +{{- if .Values.createOperator }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "external-secrets.fullname" . }} + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + {{- with .Values.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "external-secrets.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "external-secrets.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "external-secrets.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} + {{- with .Values.podSecurityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 8 }} + {{- end }} + {{- end }} + hostNetwork: {{ .Values.hostNetwork }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 12 }} + {{- end }} + {{- end }} + image: {{ include "external-secrets.image" (dict "chartAppVersion" .Chart.AppVersion "image" .Values.image) | trim }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if or (.Values.leaderElect) (.Values.scopedNamespace) (.Values.processClusterStore) (.Values.processClusterExternalSecret) (.Values.concurrent) (.Values.extraArgs) }} + args: + {{- if .Values.leaderElect }} + - --enable-leader-election=true + {{- end }} + {{- if .Values.scopedNamespace }} + - --namespace={{ .Values.scopedNamespace }} + {{- end }} + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + - --enable-cluster-store-reconciler=false + - --enable-cluster-external-secret-reconciler=false + {{- else }} + {{- if not .Values.processClusterStore }} + - --enable-cluster-store-reconciler=false + {{- end }} + {{- if not .Values.processClusterExternalSecret }} + - --enable-cluster-external-secret-reconciler=false + {{- end }} + {{- end }} + {{- if not .Values.processPushSecret }} + - --enable-push-secret-reconciler=false + {{- end }} + {{- if .Values.controllerClass }} + - --controller-class={{ .Values.controllerClass }} + {{- end }} + {{- if .Values.extendedMetricLabels }} + - --enable-extended-metric-labels={{ .Values.extendedMetricLabels }} + {{- end }} + {{- if .Values.concurrent }} + - --concurrent={{ .Values.concurrent }} + {{- end }} + {{- range $key, $value := .Values.extraArgs }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + {{- end }} + - --metrics-addr=:{{ .Values.metrics.listen.port }} + - --loglevel={{ .Values.log.level }} + - --zap-time-encoding={{ .Values.log.timeEncoding }} + ports: + - containerPort: {{ .Values.metrics.listen.port }} + protocol: TCP + name: metrics + {{- with .Values.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.extraVolumeMounts }} + volumeMounts: + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.extraContainers }} + {{ toYaml .Values.extraContainers | nindent 8}} + {{- end }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- if .Values.dnsConfig }} + dnsConfig: + {{- toYaml .Values.dnsConfig | nindent 8 }} + {{- end }} + {{- if .Values.extraVolumes }} + volumes: + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector | default .Values.global.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity | default .Values.global.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations | default .Values.global.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints | default .Values.global.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + {{- if .Values.podSpecExtra }} + {{- toYaml .Values.podSpecExtra | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/extra-manifests.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/extra-manifests.yaml new file mode 100644 index 0000000000..1dfe8f48fe --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/extra-manifests.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraObjects }} +--- +{{ include "external-secrets.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/poddisruptionbudget.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000000..7b75ca3f4e --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/poddisruptionbudget.yaml @@ -0,0 +1,19 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "external-secrets.fullname" . }}-pdb + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +spec: + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "external-secrets.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/rbac.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/rbac.yaml new file mode 100644 index 0000000000..4f4ab48fe8 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/rbac.yaml @@ -0,0 +1,301 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +{{- if and .Values.scopedNamespace .Values.scopedRBAC }} +kind: Role +{{- else }} +kind: ClusterRole +{{- end }} +metadata: + name: {{ include "external-secrets.fullname" . }}-controller + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + namespace: {{ .Values.scopedNamespace | quote }} + {{- end }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +rules: + - apiGroups: + - "external-secrets.io" + resources: + - "secretstores" + - "clustersecretstores" + - "externalsecrets" + - "clusterexternalsecrets" + - "pushsecrets" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "external-secrets.io" + resources: + - "externalsecrets" + - "externalsecrets/status" + - "externalsecrets/finalizers" + - "secretstores" + - "secretstores/status" + - "secretstores/finalizers" + - "clustersecretstores" + - "clustersecretstores/status" + - "clustersecretstores/finalizers" + - "clusterexternalsecrets" + - "clusterexternalsecrets/status" + - "clusterexternalsecrets/finalizers" + - "pushsecrets" + - "pushsecrets/status" + - "pushsecrets/finalizers" + verbs: + - "get" + - "update" + - "patch" + - apiGroups: + - "generators.external-secrets.io" + resources: + - "acraccesstokens" + - "ecrauthorizationtokens" + - "fakes" + - "gcraccesstokens" + - "githubaccesstokens" + - "passwords" + - "vaultdynamicsecrets" + - "webhooks" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "" + resources: + - "serviceaccounts" + - "namespaces" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "" + resources: + - "configmaps" + verbs: + - "get" + - "list" + - "watch" + - apiGroups: + - "" + resources: + - "secrets" + verbs: + - "get" + - "list" + - "watch" + - "create" + - "update" + - "delete" + - "patch" + - apiGroups: + - "" + resources: + - "serviceaccounts/token" + verbs: + - "create" + - apiGroups: + - "" + resources: + - "events" + verbs: + - "create" + - "patch" + - apiGroups: + - "external-secrets.io" + resources: + - "externalsecrets" + verbs: + - "create" + - "update" + - "delete" +--- +apiVersion: rbac.authorization.k8s.io/v1 +{{- if and .Values.scopedNamespace .Values.scopedRBAC }} +kind: Role +{{- else }} +kind: ClusterRole +{{- end }} +metadata: + name: {{ include "external-secrets.fullname" . }}-view + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + namespace: {{ .Values.scopedNamespace | quote }} + {{- end }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + 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: + - "external-secrets.io" + resources: + - "externalsecrets" + - "secretstores" + - "clustersecretstores" + - "pushsecrets" + verbs: + - "get" + - "watch" + - "list" + - apiGroups: + - "generators.external-secrets.io" + resources: + - "acraccesstokens" + - "ecrauthorizationtokens" + - "fakes" + - "gcraccesstokens" + - "githubaccesstokens" + - "passwords" + - "vaultdynamicsecrets" + - "webhooks" + verbs: + - "get" + - "watch" + - "list" +--- +apiVersion: rbac.authorization.k8s.io/v1 +{{- if and .Values.scopedNamespace .Values.scopedRBAC }} +kind: Role +{{- else }} +kind: ClusterRole +{{- end }} +metadata: + name: {{ include "external-secrets.fullname" . }}-edit + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + namespace: {{ .Values.scopedNamespace | quote }} + {{- end }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: + - apiGroups: + - "external-secrets.io" + resources: + - "externalsecrets" + - "secretstores" + - "clustersecretstores" + - "pushsecrets" + verbs: + - "create" + - "delete" + - "deletecollection" + - "patch" + - "update" + - apiGroups: + - "generators.external-secrets.io" + resources: + - "acraccesstokens" + - "ecrauthorizationtokens" + - "fakes" + - "gcraccesstokens" + - "githubaccesstokens" + - "passwords" + - "vaultdynamicsecrets" + - "webhooks" + verbs: + - "create" + - "delete" + - "deletecollection" + - "patch" + - "update" +--- +apiVersion: rbac.authorization.k8s.io/v1 +{{- if and .Values.scopedNamespace .Values.scopedRBAC }} +kind: RoleBinding +{{- else }} +kind: ClusterRoleBinding +{{- end }} +metadata: + name: {{ include "external-secrets.fullname" . }}-controller + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + namespace: {{ .Values.scopedNamespace | quote }} + {{- end }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + {{- if and .Values.scopedNamespace .Values.scopedRBAC }} + kind: Role + {{- else }} + kind: ClusterRole + {{- end }} + name: {{ include "external-secrets.fullname" . }}-controller +subjects: + - name: {{ include "external-secrets.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} + kind: ServiceAccount +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "external-secrets.fullname" . }}-leaderelection + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - "configmaps" + resourceNames: + - "external-secrets-controller" + verbs: + - "get" + - "update" + - "patch" + - apiGroups: + - "" + resources: + - "configmaps" + verbs: + - "create" + - apiGroups: + - "coordination.k8s.io" + resources: + - "leases" + verbs: + - "get" + - "create" + - "update" + - "patch" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "external-secrets.fullname" . }}-leaderelection + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "external-secrets.fullname" . }}-leaderelection +subjects: + - kind: ServiceAccount + name: {{ include "external-secrets.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} +{{- if .Values.rbac.servicebindings.create }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "external-secrets.fullname" . }}-servicebindings + labels: + servicebinding.io/controller: "true" + {{- include "external-secrets.labels" . | nindent 4 }} +rules: + - apiGroups: + - "external-secrets.io" + resources: + - "externalsecrets" + verbs: + - "get" + - "list" + - "watch" +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/service.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/service.yaml new file mode 100644 index 0000000000..94859a34ec --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/service.yaml @@ -0,0 +1,28 @@ +{{- if .Values.metrics.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-metrics + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + {{- with .Values.metrics.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: {{ .Values.metrics.service.port }} + protocol: TCP + targetPort: metrics + name: metrics + selector: + {{- include "external-secrets.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/serviceaccount.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/serviceaccount.yaml new file mode 100644 index 0000000000..ceaa98e1c5 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "external-secrets.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/servicemonitor.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/servicemonitor.yaml new file mode 100644 index 0000000000..31451791ae --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/servicemonitor.yaml @@ -0,0 +1,164 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) .Values.serviceMonitor.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-metrics + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: {{ .Values.metrics.service.port }} + protocol: TCP + name: metrics + selector: + {{- include "external-secrets.selectorLabels" . | nindent 4 }} +--- +apiVersion: "monitoring.coreos.com/v1" +kind: ServiceMonitor +metadata: + labels: + {{- include "external-secrets.labels" . | nindent 4 }} +{{- if .Values.serviceMonitor.additionalLabels }} +{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} + name: {{ include "external-secrets.fullname" . }}-metrics + namespace: {{ .Values.serviceMonitor.namespace | default (include "external-secrets.namespace" .) | quote }} +spec: + selector: + matchLabels: + {{- include "external-secrets.selectorLabels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ template "external-secrets.namespace" . }} + endpoints: + - port: metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + honorLabels: {{ .Values.serviceMonitor.honorLabels }} + {{- with .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} +--- +{{- if .Values.webhook.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook-metrics + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook-metrics.labels" . | nindent 4 }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: {{ .Values.webhook.metrics.service.port }} + protocol: TCP + name: metrics + selector: + {{- include "external-secrets-webhook.selectorLabels" . | nindent 4 }} +--- +apiVersion: "monitoring.coreos.com/v1" +kind: ServiceMonitor +metadata: + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} +{{- if .Values.serviceMonitor.additionalLabels }} +{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} + name: {{ include "external-secrets.fullname" . }}-webhook-metrics + namespace: {{ .Values.serviceMonitor.namespace | default (include "external-secrets.namespace" .) | quote }} +spec: + selector: + matchLabels: + {{- include "external-secrets-webhook-metrics.labels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ template "external-secrets.namespace" . }} + endpoints: + - port: metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + honorLabels: {{ .Values.serviceMonitor.honorLabels }} + {{- with .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} +--- +{{- if .Values.certController.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-cert-controller-metrics + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-cert-controller-metrics.labels" . | nindent 4 }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: {{ .Values.certController.metrics.listen.port }} + protocol: TCP + name: metrics + selector: + {{- include "external-secrets-cert-controller.selectorLabels" . | nindent 4 }} +--- +apiVersion: "monitoring.coreos.com/v1" +kind: ServiceMonitor +metadata: + labels: + {{- include "external-secrets-cert-controller.labels" . | nindent 4 }} +{{- if .Values.serviceMonitor.additionalLabels }} +{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} + name: {{ include "external-secrets.fullname" . }}-cert-controller-metrics + namespace: {{ .Values.serviceMonitor.namespace | default (include "external-secrets.namespace" .) | quote }} +spec: + selector: + matchLabels: + {{- include "external-secrets-cert-controller-metrics.labels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ template "external-secrets.namespace" . }} + endpoints: + - port: metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + honorLabels: {{ .Values.serviceMonitor.honorLabels }} + {{- with .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/validatingwebhook.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/validatingwebhook.yaml new file mode 100644 index 0000000000..63b39763f9 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/validatingwebhook.yaml @@ -0,0 +1,78 @@ +{{- if .Values.webhook.create }} +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: secretstore-validate + labels: + external-secrets.io/component: webhook + {{- with .Values.commonLabels }} + {{ toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + annotations: + cert-manager.io/inject-ca-from: {{ template "external-secrets.namespace" . }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} +webhooks: +- name: "validate.secretstore.external-secrets.io" + rules: + - apiGroups: ["external-secrets.io"] + apiVersions: ["v1beta1"] + operations: ["CREATE", "UPDATE", "DELETE"] + resources: ["secretstores"] + scope: "Namespaced" + clientConfig: + service: + namespace: {{ template "external-secrets.namespace" . }} + name: {{ include "external-secrets.fullname" . }}-webhook + path: /validate-external-secrets-io-v1beta1-secretstore + admissionReviewVersions: ["v1", "v1beta1"] + sideEffects: None + timeoutSeconds: 5 + +- name: "validate.clustersecretstore.external-secrets.io" + rules: + - apiGroups: ["external-secrets.io"] + apiVersions: ["v1beta1"] + operations: ["CREATE", "UPDATE", "DELETE"] + resources: ["clustersecretstores"] + scope: "Cluster" + clientConfig: + service: + namespace: {{ template "external-secrets.namespace" . }} + name: {{ include "external-secrets.fullname" . }}-webhook + path: /validate-external-secrets-io-v1beta1-clustersecretstore + admissionReviewVersions: ["v1", "v1beta1"] + sideEffects: None + timeoutSeconds: 5 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: externalsecret-validate + labels: + external-secrets.io/component: webhook + {{- with .Values.commonLabels }} + {{ toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.webhook.certManager.enabled .Values.webhook.certManager.addInjectorAnnotations }} + annotations: + cert-manager.io/inject-ca-from: {{ template "external-secrets.namespace" . }}/{{ include "external-secrets.fullname" . }}-webhook + {{- end }} +webhooks: +- name: "validate.externalsecret.external-secrets.io" + rules: + - apiGroups: ["external-secrets.io"] + apiVersions: ["v1beta1"] + operations: ["CREATE", "UPDATE", "DELETE"] + resources: ["externalsecrets"] + scope: "Namespaced" + clientConfig: + service: + namespace: {{ template "external-secrets.namespace" . }} + name: {{ include "external-secrets.fullname" . }}-webhook + path: /validate-external-secrets-io-v1beta1-externalsecret + admissionReviewVersions: ["v1", "v1beta1"] + sideEffects: None + timeoutSeconds: 5 + failurePolicy: {{ .Values.webhook.failurePolicy}} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-certificate.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-certificate.yaml new file mode 100644 index 0000000000..adb19fd95d --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-certificate.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.webhook.create .Values.webhook.certManager.enabled .Values.webhook.certManager.cert.create }} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + external-secrets.io/component: webhook + {{- with .Values.webhook.certManager.cert.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + commonName: {{ include "external-secrets.fullname" . }}-webhook + dnsNames: + - {{ include "external-secrets.fullname" . }}-webhook + - {{ include "external-secrets.fullname" . }}-webhook.{{ template "external-secrets.namespace" . }} + - {{ include "external-secrets.fullname" . }}-webhook.{{ template "external-secrets.namespace" . }}.svc + issuerRef: + {{- toYaml .Values.webhook.certManager.cert.issuerRef | nindent 4 }} + {{- with .Values.webhook.certManager.cert.duration }} + duration: {{ . | quote }} + {{- end }} + {{- with .Values.webhook.certManager.cert.renewBefore }} + renewBefore: {{ . | quote }} + {{- end }} + secretName: {{ include "external-secrets.fullname" . }}-webhook +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-deployment.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-deployment.yaml new file mode 100644 index 0000000000..7419a426b2 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-deployment.yaml @@ -0,0 +1,128 @@ +{{- if .Values.webhook.create }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + {{- with .Values.webhook.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.webhook.replicaCount }} + revisionHistoryLimit: {{ .Values.webhook.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "external-secrets-webhook.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.webhook.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 8 }} + {{- with .Values.webhook.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.webhook.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + hostNetwork: {{ .Values.webhook.hostNetwork}} + serviceAccountName: {{ include "external-secrets-webhook.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.webhook.serviceAccount.automount }} + {{- with .Values.webhook.podSecurityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: webhook + {{- with .Values.webhook.securityContext }} + {{- if and (.enabled) (gt (keys . | len) 1) }} + securityContext: + {{- include "external-secrets.renderSecurityContext" (dict "securityContext" . "context" $) | nindent 12 }} + {{- end }} + {{- end }} + image: {{ include "external-secrets.image" (dict "chartAppVersion" .Chart.AppVersion "image" .Values.webhook.image) | trim }} + imagePullPolicy: {{ .Values.webhook.image.pullPolicy }} + args: + - webhook + - --port={{ .Values.webhook.port }} + - --dns-name={{ include "external-secrets.fullname" . }}-webhook.{{ template "external-secrets.namespace" . }}.svc + - --cert-dir={{ .Values.webhook.certDir }} + - --check-interval={{ .Values.webhook.certCheckInterval }} + - --metrics-addr=:{{ .Values.webhook.metrics.listen.port }} + - --healthz-addr={{ .Values.webhook.readinessProbe.address }}:{{ .Values.webhook.readinessProbe.port }} + - --loglevel={{ .Values.webhook.log.level }} + - --zap-time-encoding={{ .Values.webhook.log.timeEncoding }} + {{- if .Values.webhook.lookaheadInterval }} + - --lookahead-interval={{ .Values.webhook.lookaheadInterval }} + {{- end }} + {{- range $key, $value := .Values.webhook.extraArgs }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + ports: + - containerPort: {{ .Values.webhook.metrics.listen.port }} + protocol: TCP + name: metrics + - containerPort: {{ .Values.webhook.port }} + protocol: TCP + name: webhook + readinessProbe: + httpGet: + port: {{ .Values.webhook.readinessProbe.port }} + path: /readyz + initialDelaySeconds: 20 + periodSeconds: 5 + {{- with .Values.webhook.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.webhook.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: certs + mountPath: {{ .Values.webhook.certDir }} + readOnly: true + {{- if .Values.webhook.extraVolumeMounts }} + {{- toYaml .Values.webhook.extraVolumeMounts | nindent 12 }} + {{- end }} + volumes: + - name: certs + secret: + secretName: {{ include "external-secrets.fullname" . }}-webhook + {{- if .Values.webhook.extraVolumes }} + {{- toYaml .Values.webhook.extraVolumes | nindent 8 }} + {{- end }} + {{- with .Values.webhook.nodeSelector | default .Values.global.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.affinity | default .Values.global.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.tolerations | default .Values.global.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.topologySpreadConstraints | default .Values.global.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.webhook.priorityClassName }} + priorityClassName: {{ .Values.webhook.priorityClassName }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-poddisruptionbudget.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-poddisruptionbudget.yaml new file mode 100644 index 0000000000..58345ba689 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-poddisruptionbudget.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.webhook.create .Values.webhook.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook-pdb + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + external-secrets.io/component: webhook +spec: + {{- if .Values.webhook.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.webhook.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.webhook.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.webhook.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "external-secrets-webhook.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-secret.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-secret.yaml new file mode 100644 index 0000000000..fa7760ed64 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-secret.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.webhook.create (not .Values.webhook.certManager.enabled) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + external-secrets.io/component: webhook + {{- with .Values.webhook.secretAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-service.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-service.yaml new file mode 100644 index 0000000000..59dbddc953 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-service.yaml @@ -0,0 +1,37 @@ +{{- if .Values.webhook.create }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "external-secrets.fullname" . }}-webhook + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + external-secrets.io/component: webhook + {{- if .Values.webhook.metrics.service.enabled }} + {{- with .Values.webhook.metrics.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: ClusterIP + {{- if .Values.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.service.ipFamilies }} + ipFamilies: {{ .Values.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + ports: + - port: 443 + targetPort: {{ .Values.webhook.port }} + protocol: TCP + name: webhook + {{- if .Values.webhook.metrics.service.enabled }} + - port: {{ .Values.webhook.metrics.service.port }} + protocol: TCP + targetPort: metrics + name: metrics + {{- end }} + selector: + {{- include "external-secrets-webhook.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/templates/webhook-serviceaccount.yaml b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-serviceaccount.yaml new file mode 100644 index 0000000000..1936218425 --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/templates/webhook-serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.webhook.create .Values.webhook.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "external-secrets-webhook.serviceAccountName" . }} + namespace: {{ template "external-secrets.namespace" . }} + labels: + {{- include "external-secrets-webhook.labels" . | nindent 4 }} + {{- with .Values.webhook.serviceAccount.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.webhook.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/external-secrets/external-secrets/0.10.2/values.yaml b/charts/external-secrets/external-secrets/0.10.2/values.yaml new file mode 100644 index 0000000000..19525ad8ad --- /dev/null +++ b/charts/external-secrets/external-secrets/0.10.2/values.yaml @@ -0,0 +1,532 @@ +global: + nodeSelector: {} + tolerations: [] + topologySpreadConstraints: [] + affinity: {} + compatibility: + openshift: + # -- Manages the securityContext properties to make them compatible with OpenShift. + # Possible values: + # auto - Apply configurations if it is detected that OpenShift is the target platform. + # force - Always apply configurations. + # disabled - No modification applied. + adaptSecurityContext: auto + +replicaCount: 1 + +bitwarden-sdk-server: + enabled: false + +# -- Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) +revisionHistoryLimit: 10 + +image: + repository: ghcr.io/external-secrets/external-secrets + pullPolicy: IfNotPresent + # -- The image tag to use. The default is the chart appVersion. + tag: "" + # -- The flavour of tag you want to use + # There are different image flavours available, like distroless and ubi. + # Please see GitHub release notes for image tags for these flavors. + # By default, the distroless image is used. + flavour: "" + +# -- If set, install and upgrade CRDs through helm chart. +installCRDs: true + +crds: + # -- If true, create CRDs for Cluster External Secret. + createClusterExternalSecret: true + # -- If true, create CRDs for Cluster Secret Store. + createClusterSecretStore: true + # -- If true, create CRDs for Push Secret. + createPushSecret: true + annotations: {} + conversion: + enabled: true + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" +namespaceOverride: "" + +# -- Additional labels added to all helm chart resources. +commonLabels: {} + +# -- If true, external-secrets will perform leader election between instances to ensure no more +# than one instance of external-secrets operates at a time. +leaderElect: false + +# -- If set external secrets will filter matching +# Secret Stores with the appropriate controller values. +controllerClass: "" + +# -- If true external secrets will use recommended kubernetes +# annotations as prometheus metric labels. +extendedMetricLabels: false + +# -- If set external secrets are only reconciled in the +# provided namespace +scopedNamespace: "" + +# -- Must be used with scopedNamespace. If true, create scoped RBAC roles under the scoped namespace +# and implicitly disable cluster stores and cluster external secrets +scopedRBAC: false + +# -- if true, the operator will process cluster external secret. Else, it will ignore them. +processClusterExternalSecret: true + +# -- if true, the operator will process cluster store. Else, it will ignore them. +processClusterStore: true + +# -- if true, the operator will process push secret. Else, it will ignore them. +processPushSecret: true + +# -- Specifies whether an external secret operator deployment be created. +createOperator: true + +# -- Specifies the number of concurrent ExternalSecret Reconciles external-secret executes at +# a time. +concurrent: 1 +# -- Specifices Log Params to the Webhook +log: + level: info + timeEncoding: epoch +service: + # -- Set the ip family policy to configure dual-stack see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services) + ipFamilyPolicy: "" + # -- Sets the families that should be supported and the order in which they should be applied to ClusterIP as well. Can be IPv4 and/or IPv6. + ipFamilies: [] + +serviceAccount: + # -- Specifies whether a service account should be created. + create: true + # -- Automounts the service account token in all containers of the pod + automount: true + # -- Annotations to add to the service account. + annotations: {} + # -- Extra Labels to add to the service account. + extraLabels: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template. + name: "" + +rbac: + # -- Specifies whether role and rolebinding resources should be created. + create: true + + servicebindings: + # -- Specifies whether a clusterrole to give servicebindings read access should be created. + create: true + +## -- Extra environment variables to add to container. +extraEnv: [] + +## -- Map of extra arguments to pass to container. +extraArgs: {} + +## -- Extra volumes to pass to pod. +extraVolumes: [] + +## -- Extra Kubernetes objects to deploy with the helm chart +extraObjects: [] + +## -- Extra volumes to mount to the container. +extraVolumeMounts: [] + +## -- Extra containers to add to the pod. +extraContainers: [] + +# -- Annotations to add to Deployment +deploymentAnnotations: {} + +# -- Annotations to add to Pod +podAnnotations: {} + +podLabels: {} + +podSecurityContext: + enabled: true + # fsGroup: 2000 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + enabled: true + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + +resources: {} + # requests: + # cpu: 10m + # memory: 32Mi + +serviceMonitor: + # -- Specifies whether to create a ServiceMonitor resource for collecting Prometheus metrics + enabled: false + + # -- namespace where you want to install ServiceMonitors + namespace: "" + + # -- Additional labels + additionalLabels: {} + + # -- Interval to scrape metrics + interval: 30s + + # -- Timeout if metrics can't be retrieved in given time interval + scrapeTimeout: 25s + + # -- Let prometheus add an exported_ prefix to conflicting labels + honorLabels: false + + # -- Metric relabel configs to apply to samples before ingestion. [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs) + metricRelabelings: [] + # - action: replace + # regex: (.*) + # replacement: $1 + # sourceLabels: + # - exported_namespace + # targetLabel: namespace + + # -- Relabel configs to apply to samples before ingestion. [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) + relabelings: [] + # - sourceLabels: [__meta_kubernetes_pod_node_name] + # separator: ; + # regex: ^(.*)$ + # targetLabel: nodename + # replacement: $1 + # action: replace + +metrics: + + listen: + port: 8080 + + service: + # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics + enabled: false + + # -- Metrics service port to scrape + port: 8080 + + # -- Additional service annotations + annotations: {} + +nodeSelector: {} + +tolerations: [] + +topologySpreadConstraints: [] + +affinity: {} + +# -- Pod priority class name. +priorityClassName: "" + +# -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +podDisruptionBudget: + enabled: false + minAvailable: 1 + # maxUnavailable: 1 + +# -- Run the controller on the host network +hostNetwork: false + +webhook: + # -- Specifies whether a webhook deployment be created. + create: true + # -- Specifices the time to check if the cert is valid + certCheckInterval: "5m" + # -- Specifices the lookaheadInterval for certificate validity + lookaheadInterval: "" + replicaCount: 1 + # -- Specifices Log Params to the Webhook + log: + level: info + timeEncoding: epoch + # -- Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) + revisionHistoryLimit: 10 + + certDir: /tmp/certs + # -- Specifies whether validating webhooks should be created with failurePolicy: Fail or Ignore + failurePolicy: Fail + # -- Specifies if webhook pod should use hostNetwork or not. + hostNetwork: false + image: + repository: ghcr.io/external-secrets/external-secrets + pullPolicy: IfNotPresent + # -- The image tag to use. The default is the chart appVersion. + tag: "" + # -- The flavour of tag you want to use + flavour: "" + imagePullSecrets: [] + nameOverride: "" + fullnameOverride: "" + # -- The port the webhook will listen to + port: 10250 + rbac: + # -- Specifies whether role and rolebinding resources should be created. + create: true + serviceAccount: + # -- Specifies whether a service account should be created. + create: true + # -- Automounts the service account token in all containers of the pod + automount: true + # -- Annotations to add to the service account. + annotations: {} + # -- Extra Labels to add to the service account. + extraLabels: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template. + name: "" + nodeSelector: {} + + certManager: + # -- Enabling cert-manager support will disable the built in secret and + # switch to using cert-manager (installed separately) to automatically issue + # and renew the webhook certificate. This chart does not install + # cert-manager for you, See https://cert-manager.io/docs/ + enabled: false + # -- Automatically add the cert-manager.io/inject-ca-from annotation to the + # webhooks and CRDs. As long as you have the cert-manager CA Injector + # enabled, this will automatically setup your webhook's CA to the one used + # by cert-manager. See https://cert-manager.io/docs/concepts/ca-injector + addInjectorAnnotations: true + cert: + # -- Create a certificate resource within this chart. See + # https://cert-manager.io/docs/usage/certificate/ + create: true + # -- For the Certificate created by this chart, setup the issuer. See + # https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.IssuerSpec + issuerRef: + group: cert-manager.io + kind: "Issuer" + name: "my-issuer" + # -- Set the requested duration (i.e. lifetime) of the Certificate. See + # https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec + # One year by default. + duration: "8760h" + # -- How long before the currently issued certificate’s expiry + # cert-manager should renew the certificate. See + # https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec + # Note that renewBefore should be greater than .webhook.lookaheadInterval + # since the webhook will check this far in advance that the certificate is + # valid. + renewBefore: "" + # -- Add extra annotations to the Certificate resource. + annotations: {} + + tolerations: [] + + topologySpreadConstraints: [] + + affinity: {} + + # -- Pod priority class name. + priorityClassName: "" + + # -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + podDisruptionBudget: + enabled: false + minAvailable: 1 + # maxUnavailable: 1 + + metrics: + + listen: + port: 8080 + + service: + # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics + enabled: false + + # -- Metrics service port to scrape + port: 8080 + + # -- Additional service annotations + annotations: {} + + + readinessProbe: + # -- Address for readiness probe + address: "" + # -- ReadinessProbe port for kubelet + port: 8081 + + + ## -- Extra environment variables to add to container. + extraEnv: [] + + ## -- Map of extra arguments to pass to container. + extraArgs: {} + + ## -- Extra volumes to pass to pod. + extraVolumes: [] + + ## -- Extra volumes to mount to the container. + extraVolumeMounts: [] + + # -- Annotations to add to Secret + secretAnnotations: {} + + # -- Annotations to add to Deployment + deploymentAnnotations: {} + + # -- Annotations to add to Pod + podAnnotations: {} + + podLabels: {} + + podSecurityContext: + enabled: true + # fsGroup: 2000 + + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + enabled: true + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + + resources: {} + # requests: + # cpu: 10m + # memory: 32Mi + +certController: + # -- Specifies whether a certificate controller deployment be created. + create: true + requeueInterval: "5m" + replicaCount: 1 + # -- Specifices Log Params to the Webhook + log: + level: info + timeEncoding: epoch + # -- Specifies the amount of historic ReplicaSets k8s should keep (see https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) + revisionHistoryLimit: 10 + + image: + repository: ghcr.io/external-secrets/external-secrets + pullPolicy: IfNotPresent + tag: "" + flavour: "" + imagePullSecrets: [] + nameOverride: "" + fullnameOverride: "" + rbac: + # -- Specifies whether role and rolebinding resources should be created. + create: true + serviceAccount: + # -- Specifies whether a service account should be created. + create: true + # -- Automounts the service account token in all containers of the pod + automount: true + # -- Annotations to add to the service account. + annotations: {} + # -- Extra Labels to add to the service account. + extraLabels: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template. + name: "" + nodeSelector: {} + + tolerations: [] + + topologySpreadConstraints: [] + + affinity: {} + + # -- Run the certController on the host network + hostNetwork: false + + # -- Pod priority class name. + priorityClassName: "" + + # -- Pod disruption budget - for more details see https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + podDisruptionBudget: + enabled: false + minAvailable: 1 + # maxUnavailable: 1 + + metrics: + + listen: + port: 8080 + + service: + # -- Enable if you use another monitoring tool than Prometheus to scrape the metrics + enabled: false + + # -- Metrics service port to scrape + port: 8080 + + # -- Additional service annotations + annotations: {} + + readinessProbe: + # -- Address for readiness probe + address: "" + # -- ReadinessProbe port for kubelet + port: 8081 + + ## -- Extra environment variables to add to container. + extraEnv: [] + + ## -- Map of extra arguments to pass to container. + extraArgs: {} + + + ## -- Extra volumes to pass to pod. + extraVolumes: [] + + ## -- Extra volumes to mount to the container. + extraVolumeMounts: [] + + # -- Annotations to add to Deployment + deploymentAnnotations: {} + + # -- Annotations to add to Pod + podAnnotations: {} + + podLabels: {} + + podSecurityContext: + enabled: true + # fsGroup: 2000 + + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + enabled: true + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + + resources: {} + # requests: + # cpu: 10m + # memory: 32Mi + +# -- Specifies `dnsPolicy` to deployment +dnsPolicy: ClusterFirst + +# -- Specifies `dnsOptions` to deployment +dnsConfig: {} + +# -- Any extra pod spec on the deployment +podSpecExtra: {} diff --git a/charts/jenkins/jenkins/5.5.12/CHANGELOG.md b/charts/jenkins/jenkins/5.5.12/CHANGELOG.md new file mode 100644 index 0000000000..1c644e6208 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/CHANGELOG.md @@ -0,0 +1,3090 @@ +# Changelog + +This file documents all notable changes to the Jenkins Helm Chart. +The release numbering uses [semantic versioning](http://semver.org). + +Use the following links to reference issues, PRs, and commits prior to v2.6.0. + +* Issue: `https://github.com/helm/charts/issues/[issue#]` +* PR: `https://github.com/helm/charts/pull/[pr#]` +* Commit: `https://github.com/helm/charts/commit/[commit]/stable/jenkins` + +The changelog until v1.5.7 was auto-generated based on git commits. +Those entries include a reference to the git commit to be able to get more details. + +## 5.5.12 + +Update `configuration-as-code` to version `1850.va_a_8c31d3158b_` + +## 5.5.11 + +Update `configuration-as-code` to version `1849.v3a_d20568000a_` + +## 5.5.10 + +Update `git` to version `5.4.1` + +## 5.5.9 + +Update `git` to version `5.4.0` + +## 5.5.8 + +Add `agent.garbageCollection` to support setting [kubernetes plugin garbage collection](https://plugins.jenkins.io/kubernetes/#plugin-content-garbage-collection-beta). + +## 5.5.7 + +Update `kubernetes` to version `4285.v50ed5f624918` + +## 5.5.6 + +Add `agent.useDefaultServiceAccount` to support omitting setting `serviceAccount` in the default pod template from `serviceAgentAccount.name`. +Add `agent.serviceAccount` to support setting the default pod template value. + +## 5.5.5 + +Update `jenkins/inbound-agent` to version `3261.v9c670a_4748a_9-1` + +## 5.5.4 + +Update `jenkins/jenkins` to version `2.462.1-jdk17` + +## 5.5.3 + +Update `git` to version `5.3.0` + +## 5.5.2 + +Update `kubernetes` to version `4280.vd919fa_528c7e` + +## 5.5.1 + +Update `kubernetes` to version `4265.v78b_d4a_1c864a_` + +## 5.5.0 + +Introduce capability of set skipTlsVerify and usageRestricted flags in additionalClouds + + +## 5.4.4 + +Update CHANGELOG.md, README.md, and UPGRADING.md for linting + +## 5.4.3 + +Update `configuration-as-code` to version `1836.vccda_4a_122a_a_e` + +## 5.4.2 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.5` + +## 5.4.1 + +Update `jenkins/jenkins` to version `2.452.3` + +## 5.4.0 + +Introduce capability of additional mountPaths and logging file paths for config reload container + +## 5.3.6 + +Update `workflow-aggregator` to version `600.vb_57cdd26fdd7` + +## 5.3.5 + +Update `kubernetes` to version `4253.v7700d91739e5` + +## 5.3.4 + +Update `jenkins/jenkins` to version `2.452.3-jdk17` +## 5.3.3 + +Update `jenkins/inbound-agent` to version `3256.v88a_f6e922152-1` + +## 5.3.2 + +Update `kubernetes` to version `4248.vfa_9517757b_b_a_` + +## 5.3.1 + +Fix Tiltfile deprecated value reference + +## 5.3.0 + +Add `controller.topologySpreadConstraints` + +## 5.2.2 + +Update `kubernetes` to version `4246.v5a_12b_1fe120e` + +## 5.2.1 + +Update `jenkins/jenkins` to version `2.452.2-jdk17` + +## 5.2.0 + +Add `agent.inheritYamlMergeStrategy` to allow configuring this setting on the default agent pod template. + +## 5.1.31 + +Update `kubernetes` to version `4245.vf5b_83f1fee6e` + +## 5.1.30 + +Add `controller.JCasC.configMapAnnotations` to allow setting annotations on the JCasC ConfigMaps. + +## 5.1.29 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.4` + +## 5.1.28 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.3` + +## 5.1.27 + +Update `kubernetes` to version `4244.v4fb_b_00994a_90` + +## 5.1.26 + +Update `kubernetes` to version `4238.v41b_3ef14a_5d8` + +## 5.1.25 + +Update `kubernetes` to version `4236.vc06f753c3234` + +## 5.1.24 + +Update `kubernetes` to version `4234.vdf3e78112369` + +## 5.1.23 + +Update `kubernetes` to version `4233.vb_67a_0e11a_039` + +## 5.1.22 + +Update `configuration-as-code` to version `1810.v9b_c30a_249a_4c` + +## 5.1.21 + +Update `kubernetes` to version `4231.vb_a_6b_8936497d` + +## 5.1.20 + +Update `kubernetes` to version `4230.vceef11cb_ca_37` + +## 5.1.19 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.2` + +## 5.1.18 + +Update `configuration-as-code` to version `1807.v0175eda_00a_20` + +## 5.1.17 + +Update `jenkins/inbound-agent` to version `3248.v65ecb_254c298-1` + +## 5.1.16 + +Update `configuration-as-code` to version `1805.v1455f39c04cf` + +## 5.1.15 + +Update `jenkins/jenkins` to version `2.452.1-jdk17` + +## 5.1.14 + +Update `kubernetes` to version `4219.v40ff98cfb_d6f` + +## 5.1.13 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.27.1` + +## 5.1.12 + +Update `git` to version `5.2.2` + +## 5.1.11 + +Update `kubernetes` to version `4214.vf10083a_42e70` + +## 5.1.10 + +Update `kubernetes` to version `4211.v08850dd0dfa_3` + +## 5.1.9 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.2` + +## 5.1.8 + +Update `kubernetes` to version `4209.vc646b_71e5269` + +## 5.1.7 + +Update `kubernetes` to version `4208.v4017b_a_27a_d67` + +## 5.1.6 + +Update `jenkins/jenkins` to version `2.440.3-jdk17` + +## 5.1.5 + +Fix Prometheus controller name. + +## 5.1.4 + +Update `docker.io/bats/bats` to version `1.11.0` + +## 5.1.3 + +Update `jenkins/jenkins` to version `2.440.2-jdk17` + +## 5.1.2 + +Update `kubernetes` to version `4203.v1dd44f5b_1cf9` + +## 5.1.1 + +Update `kubernetes` to version `4199.va_1647c280eb_2` + +## 5.1.0 + +Add `agent.restrictedPssSecurityContext` to automatically inject in the jnlp container a securityContext that is suitable for the use of the restricted Pod Security Standard + +## 5.0.20 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.1` + +## 5.0.19 + +Introduced helm-docs to automatically generate `values.yaml` documentation. + +## 5.0.18 + +Update `kubernetes` to version `4193.vded98e56cc25` + +## 5.0.17 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.26.0` + +## 5.0.16 + +Enable support for deleting plugin configuration files at startup. + +## 5.0.15 + +Fixed changelog entries for previous version bumps + + +## 5.0.14 + +Update `jenkins/jenkins` to version `2.440.1-jdk17` + +## 5.0.13 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `1.25.4` + +## 5.0.12 + +Fix controller.sidecars.additionalSidecarContainers renaming and add tests + +## 5.0.11 + +* Add controller.sidecars.configAutoReload.scheme to specify protocol scheme when connecting Jenkins configuration-as-code reload endpoint +* Add controller.sidecars.configAutoReload.skipTlsVerify to force the k8s-sidecar container to skip TLS verification when connecting to an HTTPS Jenkins configuration-as-code reload endpoint + +## 5.0.10 + +Update `jenkins/inbound-agent` to version `3206.vb_15dcf73f6a_9-3` + +## 5.0.9 + +Update `kubernetes` to version `4186.v1d804571d5d4` + +## 5.0.8 + +Update `configuration-as-code` to version `1775.v810dc950b_514` + +## 5.0.7 + +Update `docker.io/kiwigrid/k8s-sidecar` to version `docker.io/kiwigrid/k8s-sidecar` + +## 5.0.6 + +Removed `docker.io` prefix from inbound-agent image + +## 5.0.5 + +Prefixed artifacthub.io/images with `docker.io` + +## 5.0.4 + +Updated super-linter to v6. Updated README.md and CHANGELOG.md to fix linting issues. + +## 5.0.2 + +Update `git` to version `5.2.1` + +## 5.0.1 + +Update `docker.io/bats/bats` to version `v1.10.0` + +## 5.0.0 + + > [!CAUTION] + > Several fields have been renamed or removed. See [UPGRADING.md](./UPGRADING.md#to-500) + +The Helm Chart is now updated automatically via [Renovate](https://docs.renovatebot.com/) + +## 4.12.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.3 + +## 4.12.0 + +Add support for [generic ephemeral storage](https://github.com/jenkinsci/kubernetes-plugin/pull/1489) in `agent.volumes` and `agents.workspaceVolume`. + +| plugin | old version | new version | +|------------|---------------------|--------------------| +| kubernetes | 4029.v5712230ccb_f8 | 4174.v4230d0ccd951 | + +## 4.11.2 + +Fixed documentation for controller.initScripts. + +## 4.11.1 + +Updated helm-unittest and made unittests compatible. + +## 4.11.0 + +Add multi-cloud support. + +## 4.10.0 + +Bumped Jenkins inbound agent from 3107.v665000b_51092-15 to 3192.v713e3b_039fb_e-5. + +## 4.9.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.2 + + +Notes about [Artifact Hub](https://artifacthub.io/packages/helm/jenkinsci/jenkins?modal=changelog) changelog processing: +- Remove empty lines +- Keep only ASCII characters (no emojis) +- One change per line +- Remove table(s) (lines starting by "|") +- Backticks aren't rendered on artifacthub.io changelog + +## 4.9.1 + +Restore artifact hub notes location in CHANGELOG.md + +## 4.9.0 + +Update base images from JDK 11 to JDK 17. + +## 4.8.6 + +Proper `artifacthub.io/changes` changelog annotation preprocessing. + +## 4.8.5 + +Fix `artifacthub.io/changes` changelog annotation added to the released chart. + +## 4.8.4 + +Add `artifacthub.io/changes` changelog annotation to the released chart. + +## 4.8.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.426.1 + +## 4.8.2 + +Add the ability to modify `retentionTimeout` and `waitForPodSec` default value in JCasC + +## 4.8.1 + +Reintroduces changes from 4.7.0 (reverted in 4.7.1), with additional fixes: + +- METHOD is now allowed in `env` and is not duplicated anymore +- No calls to JCasC reload endpoint from the init container + +## 4.8.0 + +Adds support for ephemeralStorage request and limit in Kubernetes plugin JCasC template + +## 4.7.4 + +Add the config-init-script checksum into the controller statefullset pod annotations to trigger restart of the pod in case of updated init scripts. + +## 4.7.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.3 + +## 4.7.1 + +Changes in 4.7.0 were reverted. + +## 4.7.0 + +Runs `config-reload` as an init container, in addition to the sidecar container, to ensure that JCasC YAMLs are present before the main Jenkins container starts. This should fix some race conditions and crashes on startup. + +## 4.6.7 + +Change jenkins-test image label to match the other jenkins images + +## 4.6.5 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.2 + +## 4.6.4 + +Introducing TPL function on variables related to hostname in `./charts/jenkins/templates/jenkins-controller-ingress.yaml` + +## 4.6.3 + +Add values to documentation + +## 4.6.2 + +Update word from hundreds to over 1800 to align with blurb at . + +## 4.6.1 + +Update `configuration-as-code` plugin to fix dependency issues with `azure-ad` plugin + +## 4.6.0 + +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksSecretKey` to allow overriding the default secret key containing the JKS file. +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName` to allow getting the JKS password from a different secret. +Added `.Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey` to allow overriding the default secret key containing the JKS password. + +## 4.5.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.414.1 + + +## 4.5.0 + +Added `.Values.persistence.dataSource` to allow cloning home PVC from existing dataSource. + +## 4.4.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.3 + + +## 4.4.1 + +Added `.Values.agent.jnlpregistry` to allow agents to be configured with private registry. + +## 4.4.0 + +Add config keys for liveness probes on agent containers. + + +## 4.3.30 + +Update Jenkins version in controller test matching LTS version + +## 4.3.29 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.2 + + +## 4.3.28 + +Allow the kubernetes API server URL to be configurable. + +## 4.3.27 + +Bump kiwigrid/k8s-sidecar from 1.23.1 to 1.24.4 and jenkins/inbound-agent from 3107.v665000b_51092-5 to 3107.v665000b_51092-15. + +## 4.3.26 + +Fix various typos in the chart documentation. + +## 4.3.25 + +| plugin | old version | new version | +|-----------------------|----------------------|-----------------------| +| kubernetes | 3900.va_dce992317b_4 | 3937.vd7b_82db_e347b_ | +| configuration-as-code | 1625.v27444588cc3d | 1647.ve39ca_b_829b_42 | +| git | 5.0.0 | 5.1.0 | +| ldap | 671.v2a_9192a_7419d | 682.v7b_544c9d1512 | + +## 4.3.24 + +Update Jenkins image and appVersion to jenkins lts release version 2.401.1 + + +## 4.3.23 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.3 + + +## 4.3.22 + + +Bump chart version. + +## 4.3.21 + + +Document building charts for weekly releases. + +## 4.3.20 + + +Enhance repository appearance and miscellaneous cleanup. + +## 4.3.19 + + +Comply with superlinter rules and address ShellCheck issues. + +## 4.3.18 + + +Bump kiwigrid/k8s-sidecar from 1.15.0 to 1.23.1. + +## 4.3.17 + + +Bump jenkins/inbound-agent from 4.11.2-4 to 3107.v665000b_51092-5. + +## 4.3.16 + + +Update bundled plugins: +- [ldap](https://plugins.jenkins.io/ldap/): From 2.5 to 671.v2a_9192a_7419d +- [kubernetes](https://plugins.jenkins.io/kubernetes/): From 3734.v562b_b_a_627ea_c to 3900.va_dce992317b_4 +- [workflow-aggregator](https://plugins.jenkins.io/workflow-aggregator/): From 590.v6a_d052e5a_a_b_5 to 590.v6a_d052e5a_a_b_5 +- [configuration-as-code](https://plugins.jenkins.io/configuration-as-code/): From 1569.vb_72405b_80249 to 1625.v27444588cc3d + +## 4.3.15 + + +Update bats from 1.2.1 to 1.9.0. + +## 4.3.14 + + +Update various GH actions, typo fixes, and miscellaneous chores. + +## 4.3.13 + + +Bump helm-unittest from 0.2.8 to 0.2.11. + +## 4.3.12 + + +Update wording in values.yml. + +## 4.3.11 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.2 + + +## 4.3.10 + +Correct incorrect env var definition +Disable volume mount if disableSecretMount enabled + +## 4.3.9 + +Document `.Values.agent.directConnection` in readme. +Add default value for `.Values.agent.directConnection` to `values.yaml` + +## 4.3.8 + +Added `.Values.agent.directConnection` to allow agents to be configured to connect direct to the JNLP port on the +controller, preventing the need for an external HTTP endpoint for this purpose. + +## 4.3.7 + +Added `.Values.controller.shareProcessNamespace` and `.Values.controller.httpsKeyStore.disableSecretMount` to enable sourcing TLS certs from external issuers + +## 4.3.6 + +Update Jenkins image and appVersion to jenkins lts release version 2.387.1 + +## 4.3.5 + +Added `.Values.helmtest.bats.image` and `.Values.helmtest.bats.image` to allow unit tests to be configurable. Fixes [https://github.com/jenkinsci/helm-charts/issues/683] + +## 4.3.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.3 + + +## 4.3.3 + +Removed hardcoding of chart version in tests to make maintenance easier + +## 4.3.2 + +Added `.Values.serviceAccount.extraLabels` on Service Account +Added `.Values.serviceAccountAgent.extraLabels` on Agent's Service Account + + +## 4.3.0 + +Moved use of `.Values.containerEnv` within `jenkins` Container to top of `env` block to allow for subsequent Environment Variables to reference these additional ones. + +## 4.2.21 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.2 + + +## 4.2.20 + +Fixed the `controller.prometheus.metricRelabelings` being unable to convert the value to the ServiceMonitor. +Added `controller.prometheus.relabelings` to allow relabling before scrape. +Added default values for `controller.prometheus.relabelings` and `controller.prometheus.metricRelabelings`. + +## 4.2.19 + +CronJob API version upgraded to batch/v1 + +## 4.2.18 + +Added option to set secretEnvVars. + +## 4.2.17 + +Update Jenkins image and appVersion to jenkins lts release version 2.375.1 + + +## 4.2.16 + +Fixed chart notes not rendering Jenkins URL with prefix when `controller.jenkinsUriPrefix` is set. +Fixed chart notes not rendering Jenkins URL with `https` when `controller.ingress.tls` or `controller.controller.httpsKeyStore.enable` is set. +Fixed chart notes rendering wrong JCasC URL when not using `controller.ingress`. + +## 4.2.15 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.4 + +## 4.2.14 + +Added option to mount all keys from an existing k8s secret + +## 4.2.13 + +Adding `tpl` to `controller.additionalExistingSecrets` + +## 4.2.12 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.3 + + +## 4.2.11 + +Update default plugin versions + +| plugin | old version | new version | +|-----------------------|-----------------------|------------------------| +| kubernetes | 3706.vdfb_d599579f3 | 3734.v562b_b_a_627ea_c | +| git | 4.11.5 | 4.13.0 | +| configuration-as-code | 1512.vb_79d418d5fc8 | 1569.vb_72405b_80249 | + +## 4.2.10 +Fix grammar and typos + +## 4.2.9 +Update Jenkins image and appVersion to jenkins lts release version 2.361.2 + +## 4.2.8 +Modify the condition to trigger copying jenkins_config files when configAutoReload option is disabled during Jenkins initialization + +## 4.2.7 +Support for remote URL for configuration + +## 4.2.6 +Add option to set hostnetwork for agents + +## 4.2.5 +Add an extra optional argument to extraPorts in order to specify targetPort + +## 4.2.4 +Remove k8s capibility requirements when setting priority class for controller + +## 4.2.3 Update plugin versions + +| plugin | old version | new version | +| --------------------- | --------------------- | --------------------- | +| kubernetes | 3600.v144b_cd192ca_a_ | 3706.vdfb_d599579f3 | +| workflow-aggregator | 581.v0c46fa_697ffd | 590.v6a_d052e5a_a_b_5 | +| configuration-as-code | 1429.v09b_044a_c93de | 1512.vb_79d418d5fc8 | +| git | 4.11.3 | 4.11.5 | + +Resolve version conflict between default install of plugins. + +## 4.2.2 + +Support Google Managed Prometheus + +## 4.2.1 + +Remove option to provide command and args of agent as YAML. This feature was never supported by the Jenkins Kubernetes +plugin. + +## 4.2.0 + +Add option to provide additional containers to agents + +## 4.1.18 + +Update Jenkins image and appVersion to jenkins lts release version 2.361.1 + + +## 4.1.17 + +Update Jenkins casc default settings to allow `security` configs to be provided + + +## 4.1.16 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.3 + + +## 4.1.15 + +`projectNamingStrategy` is configurable in default config. + +## 4.1.14 + +If `installPlugins` is disabled, don't create unused plugins volume. + +## 4.1.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.2 + + +## 4.1.12 + +If keystore is defined, it is now also made available in the initContainer. + +## 4.1.11 + +JCasC ConfigMaps now generate their name from the `jenkins.casc.configName` helper + +## 4.1.10 + +Update Jenkins image and appVersion to jenkins lts release version 2.346.1 + + +## 4.1.9 + +Allow setting `imagePullSecret` for backup job via `backup.imagePullSecretName` + +## 4.1.8 + +Fix path of projected secrets from `additionalExistingSecrets`. + +## 4.1.7 + +Update readme with explanation on the required environmental variable `AWS_REGION` in case of using an S3 bucket. + +## 4.1.6 + +project adminSecret, additionalSecrets and additionalExistingSecrets instead of mount with subPath + +## 4.1.5 + +Update readme to fix `JAVA_OPTS` name. + +## 4.1.4 +Update plugins + +## 4.1.3 +Update jenkins-controller-statefulset projected volumes definition + +## 4.1.1 +Added 'controller.prometheus.metricRelabelings' to allow relabling and dropping unused prometheus metrics + +## 4.1.0 + +Added `controller.sidecars.configAutoReload.envFrom`, `controller.initContainerEnvFrom`, `controller.containerEnvFrom` + +## 4.0.1 + +No code changes - CI updated to run unit tests using Helm 3.8.2. + +## 4.0.0 + +Removes automatic `remotingSecurity` setting when using a container tag older than `2.326` (introduced in [`3.11.7`](#3117)). If you're using a version older than `2.326`, you should explicitly set `.controller.legacyRemotingSecurityEnabled` to `true`. + +## 3.12.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.3 + +## 3.12.1 + +Make namespace configurable for agents and additional agents. + +## 3.12.0 + +Added a flag for disabling the default Jenkins Agent configuration. + +## 3.11.10 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.2 + +## 3.11.9 Bump configuration-as-code plugin version + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| configuration-as-code | 1.51 | 1414.v878271fc496f | + +## 3.11.8 + +Make [externalTrafficPolicy](https://kubernetes.io/docs/concepts/services-networking/service/#traffic-policies) and `loadBalancerSourceRanges` fields customizable for Agent listener service via `controller.agentListenerExternalTrafficPolicy` and `controller.loadBalancerSourceRanges`. + +## 3.11.7 + +Removed Configuration as Code `remotingSecurity` section for Jenkins 2.326 or newer. See [Documentation](https://www.jenkins.io/redirect/AdminWhitelistRule) to learn more. + +## 3.11.6 + +Update Jenkins image and appVersion to jenkins lts release version 2.332.1 + + +## 3.11.5 + +Change Backup Role name function call to match the RoleDef function call in the Backup RoleBinding + +## 3.11.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.3 + + +## 3.11.3 + +Update kiwigrid/k8s-sidecar:1.15.0 +Update jenkins/inbound-agent:4.11.2-4 + +## 3.11.2 + +Improve example for workspaceVolume. Clarify that this is not a list. + +## 3.11.1 + +Update configuration-as-code plugin to 1.55.1 + + +## 3.11.0 + +Update default plugin versions + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.31.1 | 1.31.3 | +| git | 4.10.1 | 4.10.2 | + +## 3.10.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.2 + + +## 3.10.2 + +Fix definition of startupProbe when deploying on a Kubernetes cluster < 1.16 + +## 3.10.1 + +correct VALUES_SUMMARY.md for installLatestPlugins + +## 3.10.0 + +Update default plugin versions + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.30.11 | 1.31.1 | +| git | 4.10.0 | 4.10.1 | +| configuration-as-code | 1.54 | 1.55 | + +## 3.9.4 + +Add JAVA_OPTIONS to the readme so proxy settings get picked by jenkins-plugin-cli + +## 3.9.3 + +Fix config reload request URL when httpsKeystore in use + +## 3.9.2 + +Update Jenkins image and appVersion to jenkins lts release version 2.319.1 +Update following plugins: + +* kubernetes:1.30.11 +* git:4.10.0 +* configuration-as-code:1.54 + +## 3.9.1 + +Adding `tpl` to `controller.overrideArgs` + +## 3.9.0 + +Added containerSecurityContext + +## 3.8.9 + +Fix mounting of HTTPS keystore secret when httpsKeyStore is enabled + +## 3.8.8 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.3 + +## 3.8.7 + +Adding `tpl` to `initScripts` + +## 3.8.6 + +Add `controller.tagLabel` to specify the label for the image tag, for example `jdk11` or `alpine` + +## 3.8.5 + +Move jenkins web root outside of home dir + +## 3.8.4 + +Add `controller.initConfigMap` to pass pre-existing `init.groovy.d` ConfigMaps to the controller + +## 3.8.3 + +Update missed reference to jenkins/inbound-agent:4.11-1 + +## 3.8.2 + +Update jenkins/inbound-agent:4.11-1 + +## 3.8.1 + +Update jenkins/inbound-agent:4.10-3 + +## 3.8.0 + +Update kiwigrid/k8s-sidecar:1.14.2 + +## 3.7.1 + +Update git and casc plugins versions + +## 3.7.0 + +Added the option to create AWS SecurityGroupPolicy resources + +## 3.6.2 + +Fix httpsKeyStore mount when `controller.httpsKeyStore.enable` is `true` + +## 3.6.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.2 + + +## 3.6.0 +Support custom agent pod labels + +## 3.5.20 +Disallow ingress on port 50000 when agent listener is disabled + +## 3.5.19 +Add support for specifying termination-log behaviour for Jenkins controller + +## 3.5.18 +Add support for creating a Pod Disruption Budget for Jenkins controller + +## 3.5.17 +Update workdingDir to `/home/jenkins/agent` + +## 3.5.16 +Update location of icon (wiki.jenkins.io is down) + +## 3.5.15 +Add support for adding labels to the Jenkins home Persistent Volume Claim (pvc) + +## 3.5.14 + +* Updated versions of default plugins +* Use verbose logging during plugin installation +* download the latest version of all plugin dependencies (Fixes #442) + +## 3.5.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.303.1 + +## 3.5.12 + +Added extended documentation for Backup and Restore. + +## 3.5.11 + +Sanitized the Jenkins Label + +## 3.5.10 + +Fixed `controller.customJenkinsLabels` not getting templated into the controller `labelString:` field in JCasC + +## 3.5.9 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.3 + + +## 3.5.8 + +Add parameter `backup.serviceAccount.create` to disable service account creation for backup service and `backup.serviceAccount.name` to allow change of the SA name. +`backup.annotations` was moved to `backup.serviceAccount.annotations` + +## 3.5.7 + +Enable setting `controller.serviceExternalTrafficPolicy` to set [the standard Service option](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip). `externalTrafficPolicy` denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. + +## 3.5.6 + +Add optional `controller.initContainerResources`, if set, it will change resources allocation for init controller, overwise the `controller.resources` will be used + +## 3.5.5 + +Allow to configure nodeUsageMode via `agent.nodeUsageMode` + +## 3.5.4 + +Update tests to work with unittest 0.2.6 + +## 3.5.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.2 + +## 3.5.2 + +Enable setting `controller.installLatestSpecifiedPlugins` to set whether to download the latest dependencies of any plugin that is requested to have the latest version. + +## 3.5.1 +Fix activeDeadlineSeconds wrong type bug in jenkins-backup-cronjob template + +## 3.5.0 + +Allow `controller.podAnnotations` to be render as a template + +## 3.4.1 + +Allow showRawYaml for the default agent's pod template to be customized. + +## 3.4.0 + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.275` to `kiwigrid/k8s-sidecar:1.12.2` + +## 3.3.23 + +Make `controller.ingress.resourceRootUrl` compatible with API version networking.k8s.io/v1 on k8s >= 1.19.x + +## 3.3.22 + +Update Jenkins image and appVersion to jenkins lts release version 2.289.1 + +## 3.3.21 +`persistence.mounts` additionally mount to init container to allow custom CA certificate keystore + +## 3.3.18 +Added `controller.overrideArgs` so any cli argument can be passed to the WAR. + +## 3.3.17 +Correct docs on disabling plugin installation + +## 3.3.16 +Support generating `SecretClaim` resources in order to read secrets from HashiCorp Vault into Kubernetes using `kube-vault-controller`. + +## 3.3.15 +Prevent `controller.httpsKeyStore` from improperly being quoted, leading to an invalid location on disk + +## 3.3.14 +Correct docs on disabling plugin installation + +## 3.3.13 +Update plugins + +## 3.3.12 +Add `controller.additionalExistingSecrets` property + +## 3.3.11 +Add support for disabling the Agent listener service via `controller.agentListenerEnabled`. + +## 3.3.10 +Update Jenkins image and appVersion to jenkins lts release version 2.277.4 + +## 3.3.9 +* Change helper template so user defined `agent.jenkinsUrl` value will always be used, if set +* Simplify logic for `jenkinsUrl` and `jenkinsTunnel` generation: always use fully qualified address + +## 3.3.8 +Update Jenkins image and appVersion to jenkins lts release version 2.277.3 + +## 3.3.7 +fix controller-ingress line feed bug + +## 3.3.6 + +Update Git plugin version to v4.7.1 +Update ldap plugin version to v2.5 + +## 3.3.5 + +Use tpl function for environment vars. Fixes [https://github.com/jenkinsci/helm-charts/issues/324] + +## 3.3.4 + +Update Jenkins image and appVersion to jenkins lts release version 2.277.2 + + +## 3.3.3 + +Enable setting `controller.installLatestPlugins` to set whether to download the minimum required version of all dependencies. + +## 3.3.2 + +Add `controller.additionalSecrets` documentation + +## 3.3.1 + +Add `controller.additionalSecrets` property + +## 3.3.0 + +Change default Jenkins image to `jdk11` variant + +## 3.2.6 + +Add missing `controller.jenkinsUrlProtocol` property + +## 3.2.5 + +Add additional metadata `artifacthub.io/images` for artifacthub + +## 3.2.4 +Update Jenkins image and appVersion to jenkins lts release version 2.277.1 +Update Git plugin version to v4.6.0 +Update kubernetes plugin version to v1.29.2 + +## 3.2.3 + +Fix rendering `controller.ingress.path` + +## 3.2.2 + +Added description for `controller.jenkinsUrl` value + +## 3.2.1 + +Enable setting ImagePullSecrets to controller and agent service accounts. + +## 3.2.0 + +Calculate consistent unique agent IDs to be used in pod templates. Fixes [https://github.com/jenkinsci/helm-charts/issues/270] + +## 3.1.15 + +Fix documentation for the kubernetes probes + +## 3.1.14 + +Typo in documentation + +## 3.1.13 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.4 + +## 3.1.12 + +Added GitHub Action to automate the updating of LTS releases. + +## 3.1.11 + +Enable setting controller.updateStrategy to change the update strategy for StatefulSet + +## 3.1.10 + +Fixed issue for the AgentListener where it was not possible to attribute a NodePort + +## 3.1.9 + +Upgrade kubernetes plugin to 1.29.0 and CasC plugin to 1.47 + +## 3.1.8 + +Fix init scripts config map name + +## 3.1.7 + +Fix missing newline when `httpsKeyStore` is enabled + +## 3.1.6 + +Mount controller init scripts from ConfigMap + +## 3.1.5 + +Fix `namespaceOverride` not applied when loading JCasC + +## 3.1.4 + +Update Git plugin version to v4.5.2 + +## 3.1.3 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.3 + +## 3.1.2 + +Enable setting maxRequestsPerHostStr to change the max concurrent connections to Kubernetes API + +## 3.1.1 + +Update Jenkins image and appVersion to jenkins lts release version 2.263.2 + +## 3.1.0 + +* Added `.Values.controller.podSecurityContextOverride` and `.Values.backup.podSecurityContextOverride`. +* Added simple default values tests for `jenkins-backup-cronjob.yaml`. + +## 3.0.14 + +Enable to only backup job folder instead of whole jenkins + +## 3.0.13 + +Improve Documentation around JCasc and Custom Image + +## 3.0.12 + +Added GitHub Action testing on Kind 1.16, 1.17, 1.18, 1.19 & 1.20 + +## 3.0.11 + +Fixes & unit tests for Ingress resources on Kubernetes 1.19 and above + +## 3.0.10 + +Ingress resources on Kubernetes 1.19 (or above) are created with the version `networking.k8s.io/v1` + +## 3.0.9 + +Added support for backing up to Azure Blob Storage. + +## 3.0.8 + +* Typo in documentation + +## 3.0.7 + +* Add support for setting default agent workspaceVolume + +## 3.0.6 + +Use 2.263.1 image + +## 3.0.5 + +* Update appVersion to reflect new jenkins lts release version 2.263.1 + +## 3.0.4 + +* Fix documentation for additional secret mounts + +## 3.0.3 + +* Update `README.md` with explanation on how to mount additional secrets + +## 3.0.2 + +* Fix `.Values.controller.tolerations` and `.Values.controller.nodeSelector` variable names in templates\jenkins-backup-cronjob.yaml + +## 3.0.1 + +* added 'runAsNonroot' to security context + +## 3.0.0 + +* Chart uses StatefulSet instead of Deployment +* XML configuration was removed in favor of JCasC +* chart migrated to helm 3.0.0 (apiVersion v2) +* offending terms have been removed +* values have been renamed and re-ordered to make it easier to use +* already deprecated items have been removed +* componentName for the controller is now `jenkins-controller` +* componentName for the agent is now `jenkins-agent` +* container names are now + * `init` for the init container which downloads Jenkins plugins + * `jenkins` for the Jenkins controller + * `config-reload` for the sidecar container which automatically reloads JCasC +* Updated UI tests to use official `bats/bats` image instead of `dduportal/bats` + +For migration instructions from previous versions and additional information check README.md. + +## 2.19.0 + +* Use lts version 2.249.3 +* Update kubernetes, workflow-aggregator, git and configuration-as-code plugins. +* Fail apply_config.sh script if an error occurs. + +## 2.18.2 + +Fix: `master.javaOpts` issue with quoted values + +## 2.18.1 + +Recommend installing plugins in custom image + +## 2.18.0 + +Removed /tmp volume. Making /tmp a volume causes permission issues with jmap/jstack on certain Kubernetes clusters + +## 2.17.1 + +Fix location of jenkins.war file. +It is located in `/usr/share/jenkins/jenkins.war` and can be fonfigured via `master.jenkinsWar`. + +## 2.17.0 + +Add support for plugin-installation-manager-tool + +## 2.16.0 + +Added Startup probe for Jenkins pod when Kubernetes cluster is 1.16 or newer + +## 2.15.5 + +scriptApproval is taken into account when enableXmlConfig is false. + +## 2.15.4 + +Add Tilt support for easier helm chart development. + +## 2.15.3 + +Fix error on missing `ingress.paths` value + +## 2.15.2 + +Added documentation for ingress and jenkins URL + +## 2.15.1 + +Fix priorityClassName entry in values.yaml file + +## 2.15.0 + +Added support for disabling the helm.sh/chart annotation + +## 2.14.0 + +Added support for annotations in podTemplates + +## 2.13.2 + +Add nodeSelector in the backup pod +Fix tolerations in the backup pod + +## 2.13.1 + +Update list of maintainers + +## 2.13.0 + +Added Support for websockets in the default Jcasc config +Added trailing slash to JENKINS_URL env var + +## 2.12.2 + +Added unit tests for most resources in the Helm chart. + +## 2.12.1 + +Helm chart readme update + +## 2.12.0 + +Add option to configure securityContext capabilities + +## 2.11.0 + +Added configurable security context for jenkins backup CronJob and annotations to its serviceaccount. + +## 2.10.0 + +Make activeDeadlineSeconds for backup job configurable + +## 2.9.0 + +Make namespace of PrometheusRule configurable + +## 2.8.2 + +Bumped configuration-as-code plugin version from 1.41 to 1.43. +See [configuration-as-code plugin issue #1478](https://github.com/jenkinsci/configuration-as-code-plugin/issues/1478) + +## 2.8.1 + +Fix indentation of JAVA_OPTS + +## 2.8.0 + +Add support for helm unittest and include first tests + +## 2.7.2 + +Target port of container `jenkins-sc-config` taken the value from values.yaml. + +## 2.7.0 + +Add a secondary ingress template for those who want a second ingress with different labels or annotations or whatever else. + +Example: You want /github-webhook to be on a public ingress, while the main Jenkins intance to be on a private locked down ingress. + +## 2.6.5 + +Update configScripts example + +## 2.6.4 + +Add timja as a maintainer + +## 2.6.3 + +Update k8s-sidecar image to 0.1.193 + +## 2.6.2 + +Only mount empty dir secrets-dir if either `master.enableXmlConfig` or `master.secretsFilesSecret` is set +Fixes #19 + +## 2.6.1 Do not render empty JCasC templates + +## 2.6.0 First release in jenkinsci GitHub org + +Updated readme for new location + +## 2.5.2 + +Fix as per JENKINS-47112 + +## 2.5.1 + +Support Jenkins Resource Root URL + +## 2.5.0 + +Add an option to specify that Jenkins master should be initialized only once, during first install. + +## 2.4.1 + +Reorder readme parameters into sections to facilitate chart usage and maintenance + +## 2.4.0 Update default agent image + +`jenkins/jnlp-slave` is deprected and `jenkins/inbound-agent` should be used instead. +Also updated it to newest version (4.3-4). + +## 2.3.3 correct templating of master.slaveJenkinsUrl + +Fixes #22708 + +## 2.3.2 Fix wrong value for overwritePluginsFromImage + +Fixes #23003 +Fixes #22633 + +Also fixes indentation for #23114 + +## 2.3.1 + +Always mount {{ .Values.master.jenkinsRef }}/secrets/ directory. Previous it +was mounted only when `master.enableXmlConfig` was enabled. + +## 2.3.0 + +Add an option to specify pod based on labels that can connect to master if NetworkPolicy is enabled + +## 2.2.0 increase retry for config auto reload + +Configure `REQ_RETRY_CONNECT` to `10` to give Jenkins more time to start up. + + +Value can be configured via `master.sidecars.configAutoReload.reqRetryConnect` + +## 2.1.2 updated readme + +## 2.1.1 update credentials-binding plugin to 1.23 + +## 2.1.0 + +Add support to set `runAsUser` and `runAsGroup` for `agent`. + +## 2.0.1 + +Only render authorizationStrategy and securityRealm when values are set. + +## 2.0.0 Configuration as Code now default + container does not run as root anymore + +The readme contains more details for this update. +Please note that the updated values contain breaking changes. + +## 1.27.0 Update plugin versions & sidecar container + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.25.3 | 1.25.7 | +| workflow-job | 2.38 | 2.39 | +| credentials-binding | 1.21 | 1.22 | +| configuration-as-code | 1.39 | 1.41 | + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.132` to `kiwigrid/k8s-sidecar:0.1.144` + +## 1.26.0 + +Add support to override `workingDir` for default pod template + +## 1.25.0 + +Add support for installing plugins in addition to the chart's default plugins via `master.additionalPlugins` + +## 1.24.0 + +Allow configuration of yamlMergeStrategy via `agent.yamlMergeStrategy` + +## 1.23.2 + +In the `jenkins.xml.podTemplate` helper function, allow templating of all string values under `agent.volumes` except `type` by rendering them with the `tpl` function + +## 1.23.1 + +Added auto detection for Ingress API version + +## 1.23.0 + +Allow to use an existing secret for the jenkins admin credentials + +## 1.22.0 + +Add support for UI security in the default JCasC via `master.JCasC.securityRealm` and `master.JCasC.authorizationStrategy` which deny anonymous access by default + +## 1.21.3 + +Render `agent.envVars` in kubernetes pod template JCasC + +## 1.21.2 + +Cleanup `agent.yamlTemplate` rendering in kubernetes pod template XML configuration + +## 1.21.1 + +Render `agent.nodeSelector` in the kubernetes pod template JCasC + +## 1.21.0 + +Add support for overriding Ingress paths via `master.ingress.paths` + +## 1.20.0 + +Add the following options for configuring the Kubernetes plugin. + +- master.slaveDefaultsProviderTemplate +- master.slaveJenkinsUrl +- master.slaveJenkinsTunnel +- master.slaveConnectTimeout +- master.slaveReadTimeout + +## 1.19.0 + +Add support for disabling remember me via `master.disableRememberMe` +Add support for using a different markup formatter via `master.markupFormatter` + +## 1.18.1 + +Add support for executor mode configuraton with `master.executorMode`. + +## 1.18.0 Make installation of configuration-as-code plugin explicit + +Instead of configuring the configuration-as-code plugin version via +`master.JCasC.pluginVersion` it is now installed via `master.installPlugins` + +## 1.17.2 + +Allow templating of `serviceAccount.annotations` and `serviceAccountAgent.annotations` by rendering them with the `tpl` function + +## 1.17.1 + +Add support for Persistent Volume Claim (PVC) in `agent.volumes` + +## 1.17.0 + +Render `agent.volumes` in kubernetes pod template JCasC + +## 1.16.2 + +Reverts 1.16.1 as it introduced an error #22047 + +## 1.16.1 + +Fixed a bug with master.runAsUser variable due to use wrong type for comparison. + +## 1.16.0 + +Add `master.overwritePluginsFromImage` to allow support for jenkins plugins installed in the master image to persist. + +## 1.15.0 Update plugin versions & sidecar container + +| plugin | old version | new version | +| --------------------- | ----------- | ----------- | +| kubernetes | 1.25.1 | 1.25.3 | +| workflow-job | 2.36 | 2.38 | +| git | 4.2.0 | 4.2.2 | +| configuration-as-code | 1.36 | 1.39 | + +configAutoReload container updated from `kiwigrid/k8s-sidecar:0.1.20` to `kiwigrid/k8s-sidecar:0.1.132` + +## 1.14.0 + +support auto-reload container environment variables configuration + +## 1.13.3 + +Fix wrong indent in tolerations + +## 1.13.2 + +Add support for custom ClusterIP + +## 1.13.1 + +Fix `agent.yamlTemplate` rendering in kubernetes pod template JCasC + +## 1.13.0 + +Add `master.networkPolicy.internalAgents` and `master.networkPolicy.externalAgents` stanzas to fine grained controls over where internal/external agents can connect from. Internal ones are allowed based on pod labels and (optionally) namespaces, and external ones are allowed based on IP ranges. + +## 1.12.0 Support additional agents + +Add support for easy configuration of additional agents which inherit values from `agent`. + +## 1.11.3 + +Update the kubernetes plugin from 1.24.1 to 1.25.1 and grant 'watch' permission to 'events' which is required since this plugin version. + +## 1.11.2 Configure agent.args in values.yaml + +## 1.11.1 Support for master.additionalConfig + +Fixed a bug with jenkinsHome variable in range block when master.additionalConfig is set - Helm cannot evaluate field Values in type interface {}. + +## 1.11.0 Add support for configuring custom pod templates + +Add `agent.podTemplates` option for declaring custom pod templates in the default configured kubernetes cloud. + +## 1.10.1 Only copy JCasC files if there are any + +The chart always tried to copy Configuration as Code configs even if there are none. That resulted in an error which is resolved with this. + +## 1.10.0 Remove configuration-as-code-support plugins + +In recent version of configuration-as-code-plugin this is no longer necessary. + +## 1.9.24 + +Update JCasC auto-reload docs and remove stale SSH key references from version "1.8.0 JCasC auto reload works without SSH keys" + +## 1.9.23 Support jenkinsUriPrefix when JCasC is enabled + +Fixed a bug in the configuration as code reload URL, where it wouldn't work with a jenkinsUriPrefix set. + +## 1.9.22 + +Add `master.jenkinsHome` and `master.jenkinsRef` options to use docker images derivates from Jenkins + +## 1.9.21 + +Add `master.terminationGracePeriodSeconds` option + +## 1.9.20 + +Update default plugins + +- kubernetes:1.24.1 +- workflow-job:2.36 +- workflow-aggregator:2.6 +- credentials-binding:1.21 +- git:4.2.0 +- configuration-as-code:1.36 + +## 1.9.19 + +Update docs for Helm 3 + +## 1.9.18 + +Make `jenkins-home` attachable to Azure Disks without pvc + +```yaml + volumes: + - name: jenkins-home + azureDisk: + kind: Managed + diskName: myAKSDisk + diskURI: /subscriptions//resourceGroups/MC_myAKSCluster_myAKSCluster_eastus/providers/Microsoft.Compute/disks/myAKSDisk +``` + +## 1.9.16 + +Fix PodLabel for NetworkPolicy to work if enabled + +## 1.9.14 + +Properly fix case sense in `Values.master.overwriteConfig` in `config.yaml` + +## 1.9.13 + +Fix case sense in `Values.master.overwriteConfig` in `config.yaml` + +## 1.9.12 + +Scriptapprovals are overwritten when overwriteConfig is enabled + +## 1.9.10 + +Added documentation for `persistence.storageClass`. + +## 1.9.9 +Make `master.deploymentAnnotation` configurable. + +## 1.9.8 + +Make `agent.slaveConnectTimeout` configurable: by increasing this value Jenkins will not cancel&ask k8s for a pod again, while it's on `ContainerCreating`. Useful when you have big images or autoscaling takes some time. + +## 1.9.7 Update plugin versions + +| plugin | old version | new version | +|-----------------------|-------------|-------------| +| kubernetes | 1.18.2 | 1.21.2 | +| workflow-job | 2.33 | 2.36 | +| credentials-binding | 1.19 | 1.20 | +| git | 3.11.0 | 4.0.0 | +| configuration-as-code | 1.27 | 1.32 | + +## 1.9.6 + +Enables jenkins to use keystore inorder to have native ssl support #17790 + +## 1.9.5 Enable remoting security + +`Manage Jenkins` -> `Configure Global Security` -> `Enable Agent → Master Access Control` is now enabled via configuration as code plugin + +## 1.9.4 Option to set existing secret with Google Application Default Credentials + +Google application credentials are kept in a file, which has to be mounted to a pod. You can set `gcpcredentials` in `existingSecret` as follows: + +```yaml + existingSecret: + jenkins-service-account: + gcpcredentials: application_default_credentials.json +``` + +Helm template then creates the necessary volume mounts and `GOOGLE_APPLICATION_CREDENTIALS` environmental variable. + +## 1.9.3 Fix `JAVA_OPTS` when config auto-reload is enabled + +## 1.9.2 Add support for kubernetes-credentials-provider-plugin + +[kubernetes-credentials-provider-plugin](https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/) needs permissions to get/watch/list kubernetes secrets in the namespaces where Jenkins is running. + +The necessary role binding can be created using `rbac.readSecrets` when `rbac.create` is `true`. + +To quote from the plugin documentation: + +> Because granting these permissions for secrets is not something that should be done lightly it is highly advised for security reasons that you both create a unique service account to run Jenkins as, and run Jenkins in a unique namespace. + +Therefor this is disabled by default. + +## 1.9.1 Update kubernetes plugin URL + +## 1.9.0 Change default serviceType to ClusterIP + +## 1.8.2 + +Revert fix in `1.7.10` since direct connection is now disabled by default. + +## 1.8.1 + +Add `master.schedulerName` to allow setting a Kubernetes custom scheduler + +## 1.8.0 JCasC auto reload works without SSH keys + +We make use of the fact that the Jenkins Configuration as Code Plugin can be triggered via http `POST` to `JENKINS_URL/configuration-as-code/reload`and a pre-shared key. +The sidecar container responsible for reloading config changes is now `kiwigrid/k8s-sidecar:0.1.20` instead of it's fork `shadwell/k8s-sidecar`. + +References: + +- [Triggering Configuration Reload](https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/configurationReload.md) +- [kiwigrid/k8s-sidecar](https://hub.docker.com/r/kiwigrid/k8s-sidecar) + +`master.sidecars.configAutoReload.enabled` now works using `casc.reload.token` + +## 1.7.10 + +Disable direct connection in default configuration (when kubernetes plugin version >= 1.20.2). +Note: In case direct connection is going to be used `jenkins/jnlp-slave` needs to be version `3.35-5` or newer. + +## 1.7.9 + +Prevented Jenkins Setup Wizard on new installations + +## 1.7.8 + +Extend extraPorts to be opened on the Service object, not just the container. + +## 1.7.7 + +Add persistentvolumeclaim permission to the role to support new dynamic pvc workspaces. + +## 1.7.6 + +Updated `master.slaveKubernetesNamespace` to parse helm templates. +Defined an sensible empty value to the following variables, to silence invalid warnings: + +- master.extraPorts +- master.scriptApproval +- master.initScripts +- master.JCasC.configScripts +- master.sidecars.other +- agent.envVars +- agent.volumes + +## 1.7.5 + +Fixed an issue where the JCasC won't run if JCasC auto-reload is enabled [issue #17135](https://github.com/helm/charts/issues/17135) + +## 1.7.4 + +Comments out JCasC example of jenkins.systemMessage so that it can be used by end users. Previously, an attempt to set systemMessage causes Jenkins to startup, citing duplicate JCasC settings for systemMessage [issue #13333](https://github.com/helm/charts/issues/13333) + +## 1.7.2 + +Update kubernetes-plugin to version 1.18.2 which fixes frequently encountered [JENKINS-59000](https://issues.jenkins-ci.org/plugins/servlet/mobile#issue/JENKINS-59000) + +## 1.7.1 + +Update the default requirements for jenkins-agent to 512Mi which fixes frequently encountered [issue #3723](https://github.com/helm/charts/issues/3723) + +## 1.7.0 + +[Jenkins Configuration as Code Plugin](https://github.com/jenkinsci/configuration-as-code-plugin) default configuration can now be enabled via `master.JCasC.defaultConfig`. + +JCasC default configuration includes: + +- Jenkins URL +- Admin email `master.jenkinsAdminEmail` +- crumbIssuer +- disableRememberMe: false +- mode: NORMAL +- numExecutors: {{ .Values.master.numExecutors }} +- projectNamingStrategy: "standard" +- kubernetes plugin + - containerCapStr via `agent.containerCap` + - jenkinsTunnel + - jenkinsUrl + - maxRequestsPerHostStr: "32" + - name: "kubernetes" + - namespace + - serverUrl: `"https://kubernetes.default"` + - template + - containers + - alwaysPullImage: `agent.alwaysPullImage` + - args + - command + - envVars + - image: `agent.image:agent.imageTag` + - name: `.agent.sideContainerName` + - privileged: `.agent.privileged` + - resourceLimitCpu: `agent.resources.limits.cpu` + - resourceLimitMemory: `agent.resources.limits.memory` + - resourceRequestCpu: `agent.resources.requests.cpu` + - resourceRequestMemory: `agent.resources.requests.memory` + - ttyEnabled: `agent.TTYEnabled` + - workingDir: "/home/jenkins" + - idleMinutes: `agent.idleMinutes` + - instanceCap: 2147483647 + - imagePullSecrets: + - name: `.agent.imagePullSecretName` + - label + - name + - nodeUsageMode: "NORMAL" + - podRetention: `agent.podRetention` + - serviceAccount + - showRawYaml: true + - slaveConnectTimeoutStr: "100" + - yaml: `agent.yamlTemplate` + - yamlMergeStrategy: "override" +- security: + - apiToken: + - creationOfLegacyTokenEnabled: false + - tokenGenerationOnCreationEnabled: false + - usageStatisticsEnabled: true + +Example `values.yaml` which enables JCasC, it's default config and configAutoReload: + +```yaml +master: + JCasC: + enabled: true + defaultConfig: true + sidecars: + configAutoReload: + enabled: true +``` + +add master.JCasC.defaultConfig and configure location + +- JCasC configuration is stored in template `jenkins.casc.defaults` + so that it can be used in `config.yaml` and `jcasc-config.yaml` + depending on if configAutoReload is enabled or not + +- Jenkins Location (URL) is configured to provide a startin point + for the config + +## 1.6.1 + +Print error message when `master.sidecars.configAutoReload.enabled` is `true`, but the admin user can't be found to configure the SSH key. + +## 1.6.0 + +Add support for Google Cloud Storage for backup CronJob (migrating from nuvo/kube-tasks to maorfr/kube-tasks) + +## 1.5.9 + +Fixed a warning when sidecar resources are provided through a parent chart or override values + +## 1.5.8 + +Fixed an issue when master.enableXmlConfig is set to false: Always mount jenkins-secrets volume if secretsFilesSecret is set (#16512) + +## 1.5.7 + +added initial changelog (#16324) +commit: cee2ebf98 + +## 1.5.6 + +enable xml config misspelling (#16477) +commit: a125b99f9 + +## 1.5.5 + +Jenkins master label (#16469) +commit: 4802d14c9 + +## 1.5.4 + +add option enableXmlConfig (#16346) +commit: 387d97a4c + +## 1.5.3 + +extracted "jenkins.URL" into template (#16347) +commit: f2fdf5332 + +## 1.5.2 + +Fix backups when deployment has custom name (#16279) +commit: 16b89bfff + +## 1.5.1 + +Ability to set custom namespace for ServiceMonitor (#16145) +commit: 18ee6cf01 + +## 1.5.0 + +update Jenkins plugins to fix security issue (#16069) +commit: 603cf2d2b + +## 1.4.3 + +Use fixed container name (#16068) +commit: b3e4b4a49 + +## 1.4.2 + +Provide default job value (#15963) +commit: c462e2017 + +## 1.4.1 + +Add Jenkins backendconfig values (#15471) +commit: 7cc9b54c7 + +## 1.4.0 + +Change the value name for docker image tags - standartise to helm preferred value name - tag; this also allows auto-deployments using weaveworks flux (#15565) +commit: 5c3d920e7 + +## 1.3.6 + +jenkins deployment port should be target port (#15503) +commit: 83909ebe3 + +## 1.3.5 + +Add support for namespace specification (#15202) +commit: e773201a6 + +## 1.3.4 + +Adding sub-path option for scraping (#14833) +commit: e04021154 + +## 1.3.3 + +Add existingSecret to Jenkins backup AWS credentials (#13392) +commit: d9374f57d + +## 1.3.2 + +Fix JCasC version (#14992) +commit: 26a6d2b99 + +## 1.3.1 + +Update affinity for a backup cronjob (#14886) +commit: c21ed8331 + +## 1.3.0 + +only install casc support plugin when needed (#14862) +commit: a56fc0540 + +## 1.2.2 + +DNS Zone customization (#14775) +commit: da2910073 + +## 1.2.1 + +only render comment if configAutoReload is enabled (#14754) +commit: e07ead283 + +## 1.2.0 + +update plugins to latest version (#14744) +commit: 84336558e + +## 1.1.24 + +add example for EmptyDir volume (#14499) +commit: cafb60209 + +## 1.1.23 + +check if installPlugins is set before using it (#14168) +commit: 1218f0359 + +## 1.1.22 + +Support servicemonitor and alerting rules (#14124) +commit: e15a27f48 + +## 1.1.21 + +Fix: healthProbe timeouts mapping to initial delay (#13875) +commit: 825b32ece + +## 1.1.20 + +Properly handle overwrite config for additional configs (#13915) +commit: 18ce9b558 + +## 1.1.18 + +update maintainer (#13897) +commit: 223002b27 + +## 1.1.17 + +add apiVersion (#13795) +commit: cd1e5c35a + +## 1.1.16 + +allow changing of the target port to support TLS termination sidecar (#13576) +commit: a34d3bbcc + +## 1.1.15 + +fix wrong pod selector in jenkins-backup (#13542) +commit: b5df4fd7e + +## 1.1.14 + +allow templating of customInitContainers (#13536) +commit: d1e1421f4 + +## 1.1.13 + +fix #13467 (wrong deprecation message) (#13511) +commit: fbe28fa1c + +## 1.1.12 + +Correct customInitContainers Name example. (#13405) +commit: 6c6e40405 + +## 1.1.11 + +fix master.runAsUser, master.fsGroup examples (#13389) +commit: 2d7e5bf72 + +## 1.1.10 + +Ability to specify raw yaml template (#13319) +commit: 77aaa9a5f + +## 1.1.9 + +correct NOTES.txt - use master.ingress.hostname (#13318) +commit: b08ef6280 + +## 1.1.8 + +explain how to upgrade major versions (#13273) +commit: e7617a97e + +## 1.1.7 + +Add support for idleMinutes and serviceAccount (#13263) +commit: 4595ee033 + +## 1.1.6 + +Use same JENKINS_URL no matter if slaves use different namespace (#12564) +commit: 94c90339f + +## 1.1.5 + +fix deprecation checks (#13224) +commit: c7d2f8105 + +## 1.1.4 + +Fix issue introduced in #13136 (#13232) +commit: 0dbcded2e + +## 1.1.3 + +fix chart errors (#13197) +commit: 692a1e3da + +## 1.1.2 + +correct selector for jenkins pod (#13200) +commit: 4537e7fda + +## 1.1.1 + +Fix rendering of customInitContainers and lifecycle for Jenkins helm chart (#13189) +commit: e8f6b0ada + +## 1.1.0 + +Add support for openshift route in jenkins (#12973) +commit: 48c58a430 + +## 1.0.0 + +helm chart best practices (#13136) +commit: b02ae3f48 + +### Breaking changes + +- values have been renamed to follow helm chart best practices for naming conventions so + that all variables start with a lowercase letter and words are separated with camelcase + +- all resources are now using recommended standard labels + + +As a result of the label changes also the selectors of the deployment have been updated. +Those are immutable so trying an updated will cause an error like: + +```text +Error: Deployment.apps "jenkins" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app.kubernetes.io/component":"jenkins-master", "app.kubernetes.io/instance":"jenkins"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable +``` + +In order to upgrade, delete the Jenkins Deployment before upgrading: + +```console +kubectl delete deploy jenkins +``` + +## 0.40.0 + +Allow to override jenkins location protocol (#12257) +commit: 18a830626 + +## 0.39.0 + +Add possibility to add custom init-container and lifecycle for master-container (#13062) +commit: 14d043593 + +## 0.38.0 + +Support `priorityClassName` on Master Deployment (#13069) +commit: e896c62bc + +## 0.37.3 + +Add support for service account annotations in jenkins (#12969) +commit: b22774e2f + +## 0.37.2 + +fix: add hostName to ingress in values.yaml (#12946) +commit: 041045e9b + +## 0.37.1 + +Update to match actual defaults in value.yaml (#12904) +commit: 73b6d37eb + +## 0.37.0 + +Support multiple Jenkins instances in same namespace (#12748) +commit: 32ff2f343 + +## 0.36.5 + +Fix wrong comment in values.yaml (#12761) +commit: 9db8ced23 + +## 0.36.4 + +Re-add value for Ingress API Version (#12753) +commit: ecb7791b5 + +## 0.36.3 + +allow templating of volumes (#12734) +commit: adbda2ca6 + +## 0.36.2 + +Fix self-introduced whitespace bug (#12528) +commit: eec1678eb + +## 0.36.1 + +Add flag to overwrite jobs definition from values.yaml (#12427) +commit: fd349b2fc + +## 0.36.0 + +Replace OwnSshKey with AdminSshKey (#12140) (#12466) +commit: 80a8c9eb6 + +## 0.35.2 + +add note for breaking changes (#12203) +commit: e779c5a54 + +## 0.35.1 + +Allow Jenkins to run with READONLYROOTFS psp (#12338) +commit: 7c419e191 + +## 0.35.0 + +Jenkins OverwriteConfig setting also overwrites init scripts (#9468) +commit: 501335b76 + +## 0.34.1 + +Fix typo on hostname variable (#12156) +commit: 3d337d8dd + +## 0.34.0 + +Allow ingress without host rule (#11960) +commit: ddc966d1e + +## 0.33.2 + +Improve documentation - clarify that rbac is needed for autoreload (#11739) +commit: 9d75a5c34 + +## 0.33.1 + +use object for rollingUpdate (#11909) +commit: cb9cf21e8 + +## 0.33.0 + +Add hostAliases (#11701) +commit: 0b89e1094 + +## 0.32.10 + +Fix slave jnlp port always being reset when container is restarted (#11685) +commit: d7d51797b + +## 0.32.9 + +add ingress Hostname an ApiVersion to docs (#11576) +commit: 4d3e77137 + +## 0.32.8 + +Support custom master pod labels in deployment (#9714) (#11511) +commit: 9de96faa0 + +## 0.32.7 + +Fix Markdown syntax in readme (#11496) +commit: a32221a95 + +## 0.32.6 + +Added custom labels on jenkins ingress (#11466) +commit: c875d2b9b + +## 0.32.5 + +fix typo in default jenkins agent image fixes #11356 (#11463) +commit: 30adb9a91 + +## 0.32.4 + +fix incorrect Deployment when using sidecars (#11413) +commit: 362b4cef8 + +## 0.32.3 + +[]: #10131 (#11411) +commit: 49cb72055 + +## 0.32.2 + +Option to expose the slave listener port as host port (#11187) +commit: 2f85a9663 + +## 0.32.1 + +Updating Jenkins deployment fails appears rollingUpdate needs to be (#11166) +commit: 07fc9dbde + +## 0.32.0 + +Merge Sidecard configs (#11339) +commit: 3696090b9 + +## 0.31.0 + +Add option to overwrite plugins (#11231) +commit: 0e9aa00a5 + +## 0.30.0 + +Added slave Pod env vars (#8743) +commit: 1499f6608 + +## 0.29.3 + +revert indentation to previous working version (#11293) +commit: 61662f17a + +## 0.29.2 + +allow running sidecar containers for Jenkins master (#10950) +commit: 9084ce54a + +## 0.29.1 + +Indent lines related to EnableRawHtmlMarkupFormatter (#11252) +commit: 20b310c08 + +## 0.29.0 + +Jenkins Configuration as Code (#9057) +commit: c3e8c0b17 + +## 0.28.11 + +Allow to enable OWASP Markup Formatter Plugin (#10851) +commit: 9486e5ddf + +## 0.28.10 + +Fixes #1341 -- update Jenkins chart documentation (#10290) +commit: 411c81cd0 + +## 0.28.9 + +Quoted JavaOpts values (#10671) +commit: 926a843a8 + +## 0.28.8 + +Support custom labels in deployment (#9714) (#10533) +commit: 3e00b47fa + +## 0.28.7 + +separate test resources (#10597) +commit: 7b7ae2d11 + +## 0.28.6 + +allow customizing livenessProbe periodSeconds (#10534) +commit: 3c94d250d + +## 0.28.5 + +Add role kind option (#8498) +commit: e791ad124 + +## 0.28.4 + +workaround for busybox's cp (Closes: #10471) (#10497) +commit: 0d51a4187 + +## 0.28.3 + +fix parsing java options (#10140) +commit: 9448d0293 + +## 0.28.2 + +Fix job definitions in standard values.yaml (#10184) +commit: 6b6355ae7 + +## 0.28.1 + +add numExecutors as a variable in values file (#10236) +commit: d5ea2050f + +## 0.28.0 + +various (#10223) +commit: e17d2a65d + +## 0.27.0 + +add backup cronjob (#10095) +commit: 863ead8db + +## 0.26.2 + +add namespace flag for port-forwarding in jenkins notes (#10399) +commit: 846b589a9 + +## 0.26.1 + +- fixes #10267 when executed with helm template - otherwise produces an invalid template. (#10403) + commit: 266f9d839 + +## 0.26.0 + +Add subPath for jenkins-home mount (#9671) +commit: a9c76ac9b + +## 0.25.1 + +update readme to indicate the correct image that is used by default (#9915) +commit: 6aba9631c + +## 0.25.0 + +Add ability to manually set Jenkins URL (#7405) +commit: a0178fcb4 + +## 0.24.0 + +Make AuthorizationStrategy configurable (#9567) +commit: 06545b226 + +## 0.23.0 + +Update Jenkins public chart (#9296) +commit: 4e5f5918b + +## 0.22.0 + +allow to override jobs (#9004) +commit: dca9f9ab9 + +## 0.21.0 + +Simple implementation of the option to define the ingress path to the jenkins service (#8101) +commit: 013159609 + +## 0.20.2 + +Cosmetic change to remove necessity of changing "appVersion" for every new LTS release (#8866) +commit: f52af042a + +## 0.20.1 + +Added ExtraPorts to open in the master pod (#7759) +commit: 78858a2fb + +## 0.19.1 + +Fix component label in NOTES.txt ... (#8300) +commit: c5494dbfe + +## 0.19.0 + +Kubernetes 1.9 support as well as automatic apiVersion detection (#7988) +commit: 6853ad364 + +## 0.18.1 + +Respect SlaveListenerPort value in config.xml (#7220) +commit: 0a5ddac35 + +## 0.18.0 + +Allow replacement of Jenkins config with configMap. (#7450) +commit: c766da3de + +## 0.17.0 + +Add option to allow host networking (#7530) +commit: dc2eeff32 + +## 0.16.25 + +add custom jenkins labels to the build agent (#7167) +commit: 3ecde5dbf + +## 0.16.24 + +Move kubernetes and job plugins to latest versions (#7438) +commit: 019e39456 + +## 0.16.23 + +Add different Deployment Strategies based on persistence (#6132) +commit: e0a20b0b9 + +## 0.16.22 + +avoid linting errors when adding Values.Ingress.Annotations (#7425) +commit: 99eacc854 + +## 0.16.21 + +bump appVersion to reflect new jenkins lts release version 2.121.3 (#7217) +commit: 296df165d + +## 0.16.20 + +Configure kubernetes plugin for including namespace value (#7164) +commit: c0dc6cc48 + +## 0.16.19 + +make pod retention policy setting configurable (#6962) +commit: e614c1033 + +## 0.16.18 + +Update plugins version (#6988) +commit: bf8180018 + +## 0.16.17 + +Add Master.AdminPassword in readme (#6987) +commit: 13e754ad7 + +## 0.16.16 + +Added jenkins location configuration (#6573) +commit: 79de7026c + +## 0.16.15 + +use generic env var, not oracle specific env var (#6116) +commit: 6084ab4a4 + +## 0.16.14 + +Allow to specify resource requests and limits on initContainers (#6723) +commit: 942a33b1a + +## 0.16.13 + +Added support for NodePort service type for jenkens agent svc (#6571) +commit: 89a213c2b + +## 0.16.12 + +Added ability to configure multiple LoadBalancerSourceRanges (#6243) +commit: 01604ddbc + +## 0.16.11 + +Removing ContainerPort configuration as at the moment it does not work when you change this setting (#6411) +commit: e1c0468bd + +## 0.16.9 + +Fix jobs parsing for configmap by adding toYaml to jobs.yaml template (#3747) +commit: b2542a123 + +## 0.16.8 + +add jenkinsuriprefix in healthprobes (#5737) +commit: 435d7a7b9 + +## 0.16.7 + +Added the ability to switch from ClusterRoleBinding to RoleBinding. (#6190) +commit: dde03ede0 + +## 0.16.6 + +Make jenkins master pod security context optional (#6122) +commit: 63653fd59 + +## 0.16.5 + +Rework resources requests and limits (#6077) (#6077) +commit: e738f99d0 + +## 0.16.4 + +Add jenkins master pod annotations (#6313) +commit: 5e7325721 + +## 0.16.3 + +Split Jenkins readiness and liveness probe periods (#5704) +commit: fc6100c38 + +## 0.16.1 + +fix typo in jenkins readme (#5228) +commit: 3cd3f4b8b + +## 0.16.0 + +Inherit existing plugins from Jenkins image (#5409) +commit: fd93bff82 + +## 0.15.1 + +Allow NetworkPolicy.ApiVersion and Master.Ingress.ApiVersion to Differ (#5103) +commit: 78ee4ba15 + +## 0.15.0 + +Secure Defaults (#5026) +commit: 0fe90b520 + +## 0.14.6 + +Wait for up to 2 minutes before failing liveness check (#5161) +commit: 2cd3fc481 + +## 0.14.5 + +correct ImageTag setting (#4371) +commit: 8ea04174d + +## 0.14.4 + +Update jenkins/README.md (#4559) +commit: d4e6352dd + +## 0.14.3 + +Bump appVersion (#4177) +commit: 605d3d441 + +## 0.14.2 + +Master.InitContainerEnv: Init Container Env Vars (#3495) +commit: c64abe27d + +## 0.14.1 + +Allow more configuration of Jenkins agent service (#4028) +commit: fc82f39b2 + +## 0.14.0 + +Add affinity settings (#3839) +commit: 64e82fa6a + +## 0.13.5 + +bump test timeouts (#3886) +commit: cd05dd99c + +## 0.13.4 + +Add OWNERS to jenkins chart (#3881) +commit: 1c106b9c8 + +## 0.13.3 + +Add fullnameOverride support (#3705) +commit: ec8080839 + +## 0.13.2 + +Update README.md (#3638) +commit: f6d274c37 + +## 0.13.1 + +Lower initial healthcheck delay (#3463) +commit: 9b99db67c + +## 0.13.0 + +Provision credentials.xml, secrets files and jobs (#3316) +commit: d305c5961 + +## 0.12.1 + +fix the default value for nodeUsageMode. (#3299) +commit: b68d19516 + +## 0.12.0 + +Recreate pods when CustomConfigMap is true and there are changes to the ConfigMap (which is how the vanilla chart works) (#3181) +commit: 86d29f804 + +## 0.11.1 + +Optionally adds liveness and readiness probes to jenkins (#3245) +commit: 8b9aa73ee + +## 0.11.0 + +Feature/run jenkins as non root user (#2899) +commit: 8918f4175 + +## 0.10.3 + +template the version to keep them synced (#3084) +commit: 35e7fa49a + +## 0.10.2 + +Update Chart.yaml +commit: e3e617a0b + +## 0.10.1 + +Merge branch 'master' into jenkins-test-timeout +commit: 9a230a6b1 + +Double retry count for Jenkins test +commit: 129c8e824 + +Jenkins: Update readme | Master.ServiceAnnotations (#2757) +commit: 6571810bc + +## 0.10.0 + +Update Jenkins images and plugins (#2496) +commit: 2e2622682 + +## 0.9.4 + +Updating to remove the `.lock` directory as well (#2747) +commit: 6e676808f + +## 0.9.3 + +Use variable for service port when testing (#2666) +commit: d044f99be + +## 0.9.2 + +Review jenkins networkpolicy docs (#2618) +commit: 49911e458 + +Add image pull secrets to jenkins templates (#1389) +commit: 4dfae21fd + +## 0.9.1 + +Added persistent volume claim annotations (#2619) +commit: ac9e5306e + +Fix failing CI lint (#2758) +commit: 26f709f0e + +## 0.9.0 + +namespace defined templates with chart name (#2140) +commit: 408ae0b3f + +## 0.8.9 + +added useSecurity and adminUser to params (#1903) +commit: 39d2a03cd + +Use storageClassName for jenkins. (#1997) +commit: 802f6449b + +## 0.8.8 + +Remove old plugin locks before installing plugins (#1746) +commit: 6cd7b8ff4 + +promote initContainrs to podspec (#1740) +commit: fecc804fc + +## 0.8.7 + +add optional LoadBalancerIP option. (#1568) +commit: d39f11408 + +## 0.8.6 + +Fix bad key in values.yaml (#1633) +commit: dc27e5af3 + +## 0.8.5 + +Update Jenkins to support node selectors for agents. (#1532) +commit: 4af5810ff + +## 0.8.4 + +Add support for supplying JENKINS_OPTS and/or URI prefix (#1405) +commit: 6a331901a + +## 0.8.3 + +Add serviceAccountName to deployment (#1477) +commit: 0dc349b44 + +## 0.8.2 + +Remove path from ingress specification to allow other paths (#1599) +commit: e727f6b32 + +Update git plugin to 3.4.0 for CVE-2017-1000084 (#1505) +commit: 03482f995 + +## 0.8.1 + +Use consistent whitespace in template placeholders (#1437) +commit: 912f50c71 + +add configurable service annotations #1234 (#1244) +commit: 286861ca8 + +## 0.8.0 + +Jenkins v0.8.0 (#1385) +commit: 0009a2393 + +## 0.7.4 + +Use imageTag as version in config map (#1333) +commit: e8bb6ebb4 + +## 0.7.3 + +Add NetworkPolicy to Jenkins (#1228) +commit: 572b36c6d + +## 0.7.2 + +- Workflow plugin pin (#1178) + commit: ac3a0c7bc + +## 0.7.1 + +copy over plugins.txt in case of update (#1222) +commit: 75b5b1174 + +## 0.7.0 + +add jmx option (#964) +commit: 6ae8d1945 + +## 0.6.4 + +update jenkins to latest LTS 2.46.3 (#1182) +commit: ad90b4c27 + +## 0.6.3 + +Update chart maints to gh u/n (#1107) +commit: f357b77ed + +## 0.6.2 + +Add Agent.Privileged option (#957) +commit: 2cf4aced2 + +## 0.6.1 + +Upgrade jenkins to 2.46.2 (#971) +commit: 41bd742b4 + +## 0.6.0 + +Smoke test for Jenkins Chart (#944) +commit: 110441054 + +## 0.5.1 + +removed extra space from hardcoded password (#925) +commit: 85a9b9123 + +## 0.5.0 + +move config to init-container allowing use of upstream containers (#921) +commit: 1803c3d33 + +## 0.4.1 + +add ability to toggle jnlp-agent podTemplate generation (#918) +commit: accd53203 + +## 0.4.0 + +Jenkins add script approval (#916) +commit: c1746656e + +## 0.3.1 + +Update Jenkins to Latest LTS fixes #731 (#733) +commit: e9a3aed8b + +## 0.3.0 + +Added option to add Jenkins init scripts (#617) +commit: b889623d0 + +## 0.2.0 + +Add existing PVC (#716) +commit: 05271f145 + +## 0.1.15 + +use Master.ServicePort in config.xml (#769) +commit: f351f4b16 + +## 0.1.14 + +Added option to disable security on master node (#403) +commit: 3a6113d18 + +## 0.1.13 + +Added: extra mount points support for jenkins master (#474) +commit: fab0f7eb1 + +## 0.1.12 + +fix storageclass config typo (#548) +commit: 6fc0ff242 + +## 0.1.10 + +Changed default value of Kubernetes Cloud name to match one in kubernetes plugin (#404) +commit: 68351304a + +Add support for overriding the Jenkins ConfigMap (#524) +commit: f97ca53b1 + +## 0.1.9 + +Added jenkins-master ingress support (#402) +commit: d76a09588 + +## 0.1.8 + +Change description (#553) +commit: 91f5c24e1 + +Removed default Persistence.StorageClass: generic (#530) +commit: c87494c10 + +Update to the recommended pvc patterns. (#448) +commit: a7fc595aa + +Remove helm.sh/created annotations (#505) +commit: f380da2fb + +## 0.1.7 + +add support for explicit NodePort on jenkins chart (#342) +commit: f63c188da + +Add configurable loadBalancerSourceRanges for jenkins chart (#360) +commit: 44007c50e + +Update Jenkins version to current LTS (2.19.4) and Kubernetes Plugin to 0.10 (#341) +commit: 6c8678167 + +## 0.1.6 + +Add imagePullPolicy to init container (#295) +commit: 103ee1952 + +## 0.1.5 + +bump chart version with PVC metadata label additions +commit: 4aa9cf5b1 + +## 0.1.4 + +removed `*` from `jenkins/templates/NOTES.txt` +commit: 76212230b + +apply standard metadata labels to PVC's +commit: 58b730836 + +specify namespace in `kubectl get svc` commands in NOTES.txt +commit: 7d3287e81 + +Update Jenkins version to current LTS (#194) +commit: 2c0404049 + +## 0.1.1 + +escape fixed +commit: 2026e1d15 + +.status.loadBalancer.ingress[0].ip is empty in AWS +commit: 1810e37f4 + +.status.loadBalancer.ingress[0].ip is empty in AWS +commit: 3cbd3ced6 + +Remove 'Getting Started:' from various NOTES.txt. (#181) +commit: 2f63fd524 + +docs(\*): update readmes to reference chart repos (#119) +commit: c7d1bff05 + +## 0.1.0 + +Move first batch of PVC charts to stable +commit: d745f4879 diff --git a/charts/jenkins/jenkins/5.5.12/Chart.yaml b/charts/jenkins/jenkins/5.5.12/Chart.yaml new file mode 100644 index 0000000000..712a6bdcc2 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/Chart.yaml @@ -0,0 +1,54 @@ +annotations: + artifacthub.io/category: integration-delivery + artifacthub.io/changes: | + - Update `configuration-as-code` to version `1850.va_a_8c31d3158b_` + artifacthub.io/images: | + - name: jenkins + image: docker.io/jenkins/jenkins:2.462.1-jdk17 + - name: k8s-sidecar + image: docker.io/kiwigrid/k8s-sidecar:1.27.5 + - name: inbound-agent + image: jenkins/inbound-agent:3261.v9c670a_4748a_9-1 + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: Chart Source + url: https://github.com/jenkinsci/helm-charts/tree/main/charts/jenkins + - name: Jenkins + url: https://www.jenkins.io/ + - name: support + url: https://github.com/jenkinsci/helm-charts/issues + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Jenkins + catalog.cattle.io/kube-version: '>=1.14-0' + catalog.cattle.io/release-name: jenkins +apiVersion: v2 +appVersion: 2.462.1 +description: 'Jenkins - Build great things at any scale! As the leading open source + automation server, Jenkins provides over 1800 plugins to support building, deploying + and automating any project. ' +home: https://www.jenkins.io/ +icon: file://assets/icons/jenkins.svg +keywords: +- jenkins +- ci +- devops +kubeVersion: '>=1.14-0' +maintainers: +- email: maor.friedman@redhat.com + name: maorfr +- email: mail@torstenwalter.de + name: torstenwalter +- email: garridomota@gmail.com + name: mogaal +- email: wmcdona89@gmail.com + name: wmcdona89 +- email: timjacomb1@gmail.com + name: timja +name: jenkins +sources: +- https://github.com/jenkinsci/jenkins +- https://github.com/jenkinsci/docker-inbound-agent +- https://github.com/maorfr/kube-tasks +- https://github.com/jenkinsci/configuration-as-code-plugin +type: application +version: 5.5.12 diff --git a/charts/jenkins/jenkins/5.5.12/README.md b/charts/jenkins/jenkins/5.5.12/README.md new file mode 100644 index 0000000000..4ddd1faa43 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/README.md @@ -0,0 +1,706 @@ +# Jenkins + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/jenkins)](https://artifacthub.io/packages/helm/jenkinsci/jenkins) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Releases downloads](https://img.shields.io/github/downloads/jenkinsci/helm-charts/total.svg)](https://github.com/jenkinsci/helm-charts/releases) +[![Join the chat at https://app.gitter.im/#/room/#jenkins-ci:matrix.org](https://badges.gitter.im/badge.svg)](https://app.gitter.im/#/room/#jenkins-ci:matrix.org) + +[Jenkins](https://www.jenkins.io/) is the leading open source automation server, Jenkins provides over 1800 plugins to support building, deploying and automating any project. + +This chart installs a Jenkins server which spawns agents on [Kubernetes](http://kubernetes.io) utilizing the [Jenkins Kubernetes plugin](https://plugins.jenkins.io/kubernetes/). + +Inspired by the awesome work of [Carlos Sanchez](https://github.com/carlossg). + +## Get Repository Info + +```console +helm repo add jenkins https://charts.jenkins.io +helm repo update +``` + +_See [`helm repo`](https://helm.sh/docs/helm/helm_repo/) for command documentation._ + +## Install Chart + +```console +# Helm 3 +$ helm install [RELEASE_NAME] jenkins/jenkins [flags] +``` + +_See [configuration](#configuration) below._ + +_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._ + +## Uninstall Chart + +```console +# Helm 3 +$ helm uninstall [RELEASE_NAME] +``` + +This removes all the Kubernetes components associated with the chart and deletes the release. + +_See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation._ + +## Upgrade Chart + +```console +# Helm 3 +$ helm upgrade [RELEASE_NAME] jenkins/jenkins [flags] +``` + +_See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation._ + +Visit the chart's [CHANGELOG](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/CHANGELOG.md) to view the chart's release history. +For migration between major version check [migration guide](#migration-guide). + +## Building weekly releases + +The default charts target Long-Term-Support (LTS) releases of Jenkins. +To use other versions the easiest way is to update the image tag to the version you want. +You can also rebuild the chart if you want the `appVersion` field to match. + +## Configuration + +See [Customizing the Chart Before Installing](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing). +To see all configurable options with detailed comments, visit the chart's [values.yaml](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/values.yaml), or run these configuration commands: + +```console +# Helm 3 +$ helm show values jenkins/jenkins +``` + +For a summary of all configurable options, see [VALUES_SUMMARY.md](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md). + +### Configure Security Realm and Authorization Strategy + +This chart configured a `securityRealm` and `authorizationStrategy` as shown below: + +```yaml +controller: + JCasC: + securityRealm: |- + local: + allowsSignup: false + enableCaptcha: false + users: + - id: "${chart-admin-username}" + name: "Jenkins Admin" + password: "${chart-admin-password}" + authorizationStrategy: |- + loggedInUsersCanDoAnything: + allowAnonymousRead: false +``` + +With the configuration above there is only a single user. +This is fine for getting started quickly, but it needs to be adjusted for any serious environment. + +So you should adjust this to suite your needs. +That could be using LDAP / OIDC / .. as authorization strategy and use globalMatrix as authorization strategy to configure more fine-grained permissions. + +### Consider using a custom image + +This chart allows the user to specify plugins which should be installed. However, for production use cases one should consider to build a custom Jenkins image which has all required plugins pre-installed. +This way you can be sure which plugins Jenkins is using when starting up and you avoid trouble in case of connectivity issues to the Jenkins update site. + +The [docker repository](https://github.com/jenkinsci/docker) for the Jenkins image contains [documentation](https://github.com/jenkinsci/docker#preinstalling-plugins) how to do it. + +Here is an example how that can be done: + +```Dockerfile +FROM jenkins/jenkins:lts +RUN jenkins-plugin-cli --plugins kubernetes workflow-aggregator git configuration-as-code +``` + +NOTE: If you want a reproducible build then you should specify a non-floating tag for the image `jenkins/jenkins:2.249.3` and specify plugin versions. + +Once you built the image and pushed it to your registry you can specify it in your values file like this: + +```yaml +controller: + image: "registry/my-jenkins" + tag: "v1.2.3" + installPlugins: false +``` + +Notice: `installPlugins` is set to false to disable plugin download. In this case, the image `registry/my-jenkins:v1.2.3` must have the plugins specified as default value for [the `controller.installPlugins` directive](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-plugins) to ensure that the configuration side-car system works as expected. + +In case you are using a private registry you can use 'imagePullSecretName' to specify the name of the secret to use when pulling the image: + +```yaml +controller: + image: "registry/my-jenkins" + tag: "v1.2.3" + imagePullSecretName: registry-secret + installPlugins: false +``` + +### External URL Configuration + +If you are using the ingress definitions provided by this chart via the `controller.ingress` block the configured hostname will be the ingress hostname starting with `https://` or `http://` depending on the `tls` configuration. +The Protocol can be overwritten by specifying `controller.jenkinsUrlProtocol`. + +If you are not using the provided ingress you can specify `controller.jenkinsUrl` to change the URL definition. + +### Configuration as Code + +Jenkins Configuration as Code (JCasC) is now a standard component in the Jenkins project. +To allow JCasC's configuration from the helm values, the plugin [`configuration-as-code`](https://plugins.jenkins.io/configuration-as-code/) must be installed in the Jenkins Controller's Docker image (which is the case by default as specified by the [default value of the directive `controller.installPlugins`](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-plugins)). + +JCasc configuration is passed through Helm values under the key `controller.JCasC`. +The section ["Jenkins Configuration as Code (JCasC)" of the page "VALUES_SUMMARY.md"](https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/VALUES_SUMMARY.md#jenkins-configuration-as-code-jcasc) lists all the possible directives. + +In particular, you may specify custom JCasC scripts by adding sub-key under the `controller.JCasC.configScripts` for each configuration area where each corresponds to a plugin or section of the UI. + +The sub-keys (prior to `|` character) are only labels used to give the section a meaningful name. +The only restriction is they must conform to RFC 1123 definition of a DNS label, so they may only contain lowercase letters, numbers, and hyphens. + +Each key will become the name of a configuration yaml file on the controller in `/var/jenkins_home/casc_configs` (by default) and will be processed by the Configuration as Code Plugin during Jenkins startup. + +The lines after each `|` become the content of the configuration yaml file. + +The first line after this is a JCasC root element, e.g. jenkins, credentials, etc. + +Best reference is the Documentation link here: `https:///configuration-as-code`. + +The example below sets custom systemMessage: + +```yaml +controller: + JCasC: + configScripts: + welcome-message: | + jenkins: + systemMessage: Welcome to our CI\CD server. +``` + +More complex example that creates ldap settings: + +```yaml +controller: + JCasC: + configScripts: + ldap-settings: | + jenkins: + securityRealm: + ldap: + configurations: + - server: ldap.acme.com + rootDN: dc=acme,dc=uk + managerPasswordSecret: ${LDAP_PASSWORD} + groupMembershipStrategy: + fromUserRecord: + attributeName: "memberOf" +``` + +Keep in mind that default configuration file already contains some values that you won't be able to override under configScripts section. + +For example, you can not configure Jenkins URL and System Admin email address like this because of conflicting configuration error. + +Incorrect: + +```yaml +controller: + JCasC: + configScripts: + jenkins-url: | + unclassified: + location: + url: https://example.com/jenkins + adminAddress: example@mail.com +``` + +Correct: + +```yaml +controller: + jenkinsUrl: https://example.com/jenkins + jenkinsAdminEmail: example@mail.com +``` + +Further JCasC examples can be found [here](https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos). + +#### Breaking out large Config as Code scripts + +Jenkins Config as Code scripts can become quite large, and maintaining all of your scripts within one yaml file can be difficult. The Config as Code plugin itself suggests updating the `CASC_JENKINS_CONFIG` environment variable to be a comma separated list of paths for the plugin to traverse, picking up the yaml files as needed. +However, under the Jenkins helm chart, this `CASC_JENKINS_CONFIG` value is maintained through the templates. A better solution is to split your `controller.JCasC.configScripts` into separate values files, and provide each file during the helm install. + +For example, you can have a values file (e.g values_main.yaml) that defines the values described in the `VALUES_SUMMARY.md` for your Jenkins configuration: + +```yaml +jenkins: + controller: + jenkinsUrlProtocol: https + installPlugins: false + ... +``` + +In a second file (e.g values_jenkins_casc.yaml), you can define a section of your config scripts: + +```yaml +jenkins: + controller: + JCasC: + configScripts: + jenkinsCasc: | + jenkins: + disableRememberMe: false + mode: NORMAL + ... +``` + +And keep extending your config scripts by creating more files (so not all config scripts are located in one yaml file for better maintenance): + +values_jenkins_unclassified.yaml + +```yaml +jenkins: + controller: + JCasC: + configScripts: + unclassifiedCasc: | + unclassified: + ... +``` + +When installing, you provide all relevant yaml files (e.g `helm install -f values_main.yaml -f values_jenkins_casc.yaml -f values_jenkins_unclassified.yaml ...`). Instead of updating the `CASC_JENKINS_CONFIG` environment variable to include multiple paths, multiple CasC yaml files will be created in the same path `var/jenkins_home/casc_configs`. + +#### Config as Code With or Without Auto-Reload + +Config as Code changes (to `controller.JCasC.configScripts`) can either force a new pod to be created and only be applied at next startup, or can be auto-reloaded on-the-fly. +If you set `controller.sidecars.configAutoReload.enabled` to `true`, a second, auxiliary container will be installed into the Jenkins controller pod, known as a "sidecar". +This watches for changes to configScripts, copies the content onto the Jenkins file-system and issues a POST to `http:///reload-configuration-as-code` with a pre-shared key. +You can monitor this sidecar's logs using command `kubectl logs -c config-reload -f`. +If you want to enable auto-reload then you also need to configure rbac as the container which triggers the reload needs to watch the config maps: + +```yaml +controller: + sidecars: + configAutoReload: + enabled: true +rbac: + create: true +``` + +### Allow Limited HTML Markup in User-Submitted Text + +Some third-party systems (e.g. GitHub) use HTML-formatted data in their payload sent to a Jenkins webhook (e.g. URL of a pull-request being built). +To display such data as processed HTML instead of raw text set `controller.enableRawHtmlMarkupFormatter` to true. +This option requires installation of the [OWASP Markup Formatter Plugin (antisamy-markup-formatter)](https://plugins.jenkins.io/antisamy-markup-formatter/). +This plugin is **not** installed by default but may be added to `controller.additionalPlugins`. + +### Change max connections to Kubernetes API +When using agents with containers other than JNLP, The kubernetes plugin will communicate with those containers using the Kubernetes API. this changes the maximum concurrent connections +```yaml +agent: + maxRequestsPerHostStr: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Change container cleanup timeout API +For tasks that use very large images, this timeout can be increased to avoid early termination of the task while the Kubernetes pod is still deploying. +```yaml +agent: + retentionTimeout: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Change seconds to wait for pod to be running +This will change how long Jenkins will wait (seconds) for pod to be in running state. +```yaml +agent: + waitForPodSec: "32" +``` +This will change the configuration of the kubernetes "cloud" (as called by jenkins) that is created automatically as part of this helm chart. + +### Mounting Volumes into Agent Pods + +Your Jenkins Agents will run as pods, and it's possible to inject volumes where needed: + +```yaml +agent: + volumes: + - type: Secret + secretName: jenkins-mysecrets + mountPath: /var/run/secrets/jenkins-mysecrets +``` + +The supported volume types are: `ConfigMap`, `EmptyDir`, `HostPath`, `Nfs`, `PVC`, `Secret`. +Each type supports a different set of configurable attributes, defined by [the corresponding Java class](https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes). + +### NetworkPolicy + +To make use of the NetworkPolicy resources created by default, install [a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin). + +[Install](#install-chart) helm chart with network policy enabled by setting `networkPolicy.enabled` to `true`. + +You can use `controller.networkPolicy.internalAgents` and `controller.networkPolicy.externalAgents` stanzas for fine-grained controls over where internal/external agents can connect from. +Internal ones are allowed based on pod labels and (optionally) namespaces, and external ones are allowed based on IP ranges. + +### Script approval list + +`controller.scriptApproval` allows to pass function signatures that will be allowed in pipelines. +Example: + +```yaml +controller: + scriptApproval: + - "method java.util.Base64$Decoder decode java.lang.String" + - "new java.lang.String byte[]" + - "staticMethod java.util.Base64 getDecoder" +``` + +### Custom Labels + +`controller.serviceLabels` can be used to add custom labels in `jenkins-controller-svc.yaml`. +For example: + +```yaml +ServiceLabels: + expose: true +``` + +### Persistence + +The Jenkins image stores persistence under `/var/jenkins_home` path of the container. +A dynamically managed Persistent Volume Claim is used to keep the data across deployments, by default. +This is known to work in GCE, AWS, and minikube. Alternatively, a previously configured Persistent Volume Claim can be used. + +It is possible to mount several volumes using `persistence.volumes` and `persistence.mounts` parameters. +See additional `persistence` values using [configuration commands](#configuration). + +#### Existing PersistentVolumeClaim + +1. Create the PersistentVolume +2. Create the PersistentVolumeClaim +3. [Install](#install-chart) the chart, setting `persistence.existingClaim` to `PVC_NAME` + +#### Long Volume Attach/Mount Times + +Certain volume type and filesystem format combinations may experience long +attach/mount times, [10 or more minutes][K8S_VOLUME_TIMEOUT], when using +`fsGroup`. This issue may result in the following entries in the pod's event +history: + +```console +Warning FailedMount 38m kubelet, aks-default-41587790-2 Unable to attach or mount volumes: unmounted volumes=[jenkins-home], unattached volumes=[plugins plugin-dir jenkins-token-rmq2g sc-config-volume tmp jenkins-home jenkins-config secrets-dir]: timed out waiting for the condition +``` + +In these cases, experiment with replacing `fsGroup` with +`supplementalGroups` in the pod's `securityContext`. This can be achieved by +setting the `controller.podSecurityContextOverride` Helm chart value to +something like: + +```yaml +controller: + podSecurityContextOverride: + runAsNonRoot: true + runAsUser: 1000 + supplementalGroups: [1000] +``` + +This issue has been reported on [azureDisk with ext4][K8S_VOLUME_TIMEOUT] and +on [Alibaba cloud][K8S_VOLUME_TIMEOUT_ALIBABA]. + +[K8S_VOLUME_TIMEOUT]: https://github.com/kubernetes/kubernetes/issues/67014 +[K8S_VOLUME_TIMEOUT_ALIBABA]: https://github.com/kubernetes/kubernetes/issues/67014#issuecomment-698770511 + +#### Storage Class + +It is possible to define which storage class to use, by setting `persistence.storageClass` to `[customStorageClass]`. +If set to a dash (`-`), dynamic provisioning is disabled. +If the storage class is set to null or left undefined (`""`), the default provisioner is used (gp2 on AWS, standard on GKE, AWS & OpenStack). + +### Additional Secrets + +Additional secrets and Additional Existing Secrets, +can be mounted into the Jenkins controller through the chart or created using `controller.additionalSecrets` or `controller.additionalExistingSecrets`. +A common use case might be identity provider credentials if using an external LDAP or OIDC-based identity provider. +The secret may then be referenced in JCasC configuration (see [JCasC configuration](#configuration-as-code)). + +`values.yaml` controller section, referencing mounted secrets: +```yaml +controller: + # the 'name' and 'keyName' are concatenated with a '-' in between, so for example: + # an existing secret "secret-credentials" and a key inside it named "github-password" should be used in Jcasc as ${secret-credentials-github-password} + # 'name' and 'keyName' must be lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', + # and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc') + # existingSecret existing secret "secret-credentials" and a key inside it named "github-username" should be used in Jcasc as ${github-username} + # When using existingSecret no need to specify the keyName under additionalExistingSecrets. + existingSecret: secret-credentials + + additionalExistingSecrets: + - name: secret-credentials + keyName: github-username + - name: secret-credentials + keyName: github-password + - name: secret-credentials + keyName: token + + additionalSecrets: + - name: client_id + value: abc123 + - name: client_secret + value: xyz999 + JCasC: + securityRealm: | + oic: + clientId: ${client_id} + clientSecret: ${client_secret} + ... + configScripts: + jenkins-casc-configs: | + credentials: + system: + domainCredentials: + - credentials: + - string: + description: "github access token" + id: "github_app_token" + scope: GLOBAL + secret: ${secret-credentials-token} + - usernamePassword: + description: "github access username password" + id: "github_username_pass" + password: ${secret-credentials-github-password} + scope: GLOBAL + username: ${secret-credentials-github-username} +``` + +For more information, see [JCasC documentation](https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets). + +### Secret Claims from HashiCorp Vault + +It's possible for this chart to generate `SecretClaim` resources in order to automatically create and maintain Kubernetes `Secrets` from HashiCorp [Vault](https://www.vaultproject.io/) via [`kube-vault-controller`](https://github.com/roboll/kube-vault-controller) + +These `Secrets` can then be referenced in the same manner as Additional Secrets above. + +This can be achieved by defining required Secret Claims within `controller.secretClaims`, as follows: +```yaml +controller: + secretClaims: + - name: jenkins-secret + path: secret/path + - name: jenkins-short-ttl + path: secret/short-ttl-path + renew: 60 +``` + +### RBAC + +RBAC is enabled by default. If you want to disable it you will need to set `rbac.create` to `false`. + +### Adding Custom Pod Templates + +It is possible to add custom pod templates for the default configured kubernetes cloud. +Add a key under `agent.podTemplates` for each pod template. Each key (prior to `|` character) is just a label, and can be any value. +Keys are only used to give the pod template a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label characters: lowercase letters, numbers, and hyphens. Each pod template can contain multiple containers. +There's no need to add the _jnlp_ container since the kubernetes plugin will automatically inject it into the pod. +For this pod templates configuration to be loaded the following values must be set: + +```yaml +controller.JCasC.defaultConfig: true +``` + +The example below creates a python pod template in the kubernetes cloud: + +```yaml +agent: + podTemplates: + python: | + - name: python + label: jenkins-python + serviceAccount: jenkins + containers: + - name: python + image: python:3 + command: "/bin/sh -c" + args: "cat" + ttyEnabled: true + privileged: true + resourceRequestCpu: "400m" + resourceRequestMemory: "512Mi" + resourceLimitCpu: "1" + resourceLimitMemory: "1024Mi" +``` + +Best reference is `https:///configuration-as-code/reference#Cloud-kubernetes`. + +### Adding Pod Templates Using additionalAgents + +`additionalAgents` may be used to configure additional kubernetes pod templates. +Each additional agent corresponds to `agent` in terms of the configurable values and inherits all values from `agent` so you only need to specify values which differ. +For example: + +```yaml +agent: + podName: default + customJenkinsLabels: default + # set resources for additional agents to inherit + resources: + limits: + cpu: "1" + memory: "2048Mi" + +additionalAgents: + maven: + podName: maven + customJenkinsLabels: maven + # An example of overriding the jnlp container + # sideContainerName: jnlp + image: jenkins/jnlp-agent-maven + tag: latest + python: + podName: python + customJenkinsLabels: python + sideContainerName: python + image: python + tag: "3" + command: "/bin/sh -c" + args: "cat" + TTYEnabled: true +``` + +### Ingress Configuration + +This chart provides ingress resources configurable via the `controller.ingress` block. + +The simplest configuration looks like the following: + +```yaml +controller: + ingress: + enabled: true + paths: [] + apiVersion: "extensions/v1beta1" + hostName: jenkins.example.com +``` + +This snippet configures an ingress rule for exposing jenkins at `jenkins.example.com` + +You can define labels and annotations via `controller.ingress.labels` and `controller.ingress.annotations` respectively. +Additionally, you can configure the ingress tls via `controller.ingress.tls`. +By default, this ingress rule exposes all paths. +If needed this can be overwritten by specifying the wanted paths in `controller.ingress.paths` + +If you want to configure a secondary ingress e.g. you don't want the jenkins instance exposed but still want to receive webhooks you can configure `controller.secondaryingress`. +The secondaryingress doesn't expose anything by default and has to be configured via `controller.secondaryingress.paths`: + +```yaml +controller: + ingress: + enabled: true + apiVersion: "extensions/v1beta1" + hostName: "jenkins.internal.example.com" + annotations: + kubernetes.io/ingress.class: "internal" + secondaryingress: + enabled: true + apiVersion: "extensions/v1beta1" + hostName: "jenkins-scm.example.com" + annotations: + kubernetes.io/ingress.class: "public" + paths: + - /github-webhook +``` + +## Prometheus Metrics + +If you want to expose Prometheus metrics you need to install the [Jenkins Prometheus Metrics Plugin](https://github.com/jenkinsci/prometheus-plugin). +It will expose an endpoint (default `/prometheus`) with metrics where a Prometheus Server can scrape. + +If you have implemented [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator), you can set `controller.prometheus.enabled` to `true` to configure a `ServiceMonitor` and `PrometheusRule`. +If you want to further adjust alerting rules you can do so by configuring `controller.prometheus.alertingrules` + +If you have implemented Prometheus without using the operator, you can leave `controller.prometheus.enabled` set to `false`. + +### Running Behind a Forward Proxy + +The controller pod uses an Init Container to install plugins etc. If you are behind a corporate proxy it may be useful to set `controller.initContainerEnv` to add environment variables such as `http_proxy`, so that these can be downloaded. + +Additionally, you may want to add env vars for the init container, the Jenkins container, and the JVM (`controller.javaOpts`): + +```yaml +controller: + initContainerEnv: + - name: http_proxy + value: "http://192.168.64.1:3128" + - name: https_proxy + value: "http://192.168.64.1:3128" + - name: no_proxy + value: "" + - name: JAVA_OPTS + value: "-Dhttps.proxyHost=proxy_host_name_without_protocol -Dhttps.proxyPort=3128" + containerEnv: + - name: http_proxy + value: "http://192.168.64.1:3128" + - name: https_proxy + value: "http://192.168.64.1:3128" + javaOpts: >- + -Dhttp.proxyHost=192.168.64.1 + -Dhttp.proxyPort=3128 + -Dhttps.proxyHost=192.168.64.1 + -Dhttps.proxyPort=3128 +``` + +### HTTPS Keystore Configuration + +[This configuration](https://wiki.jenkins.io/pages/viewpage.action?pageId=135468777) enables jenkins to use keystore in order to serve HTTPS. +Here is the [value file section](https://wiki.jenkins.io/pages/viewpage.action?pageId=135468777#RunningJenkinswithnativeSSL/HTTPS-ConfigureJenkinstouseHTTPSandtheJKSkeystore) related to keystore configuration. +Keystore itself should be placed in front of `jenkinsKeyStoreBase64Encoded` key and in base64 encoded format. To achieve that after having `keystore.jks` file simply do this: `cat keystore.jks | base64` and paste the output in front of `jenkinsKeyStoreBase64Encoded`. +After enabling `httpsKeyStore.enable` make sure that `httpPort` and `targetPort` are not the same, as `targetPort` will serve HTTPS. +Do not set `controller.httpsKeyStore.httpPort` to `-1` because it will cause readiness and liveliness prob to fail. +If you already have a kubernetes secret that has keystore and its password you can specify its' name in front of `jenkinsHttpsJksSecretName`, You need to remember that your secret should have proper data key names `jenkins-jks-file` (or override the key name using `jenkinsHttpsJksSecretKey`) +and `https-jks-password` (or override the key name using `jenkinsHttpsJksPasswordSecretKey`; additionally you can make it get the password from a different secret using `jenkinsHttpsJksPasswordSecretName`). Example: + +```yaml +controller: + httpsKeyStore: + enable: true + jenkinsHttpsJksSecretName: '' + httpPort: 8081 + path: "/var/jenkins_keystore" + fileName: "keystore.jks" + password: "changeit" + jenkinsKeyStoreBase64Encoded: '' +``` +### AWS Security Group Policies + +To create SecurityGroupPolicies set `awsSecurityGroupPolicies.enabled` to true and add your policies. Each policy requires a `name`, array of `securityGroupIds` and a `podSelector`. Example: + +```yaml +awsSecurityGroupPolicies: + enabled: true + policies: + - name: "jenkins-controller" + securityGroupIds: + - sg-123456789 + podSelector: + matchExpressions: + - key: app.kubernetes.io/component + operator: In + values: + - jenkins-controller +``` + +### Agent Direct Connection + +Set `directConnection` to `true` to allow agents to connect directly to a given TCP port without having to negotiate a HTTP(S) connection. This can allow you to have agent connections without an external HTTP(S) port. Example: + +```yaml +agent: + jenkinsTunnel: "jenkinsci-agent:50000" + directConnection: true +``` + +## Migration Guide + +### From stable repository + +Upgrade an existing release from `stable/jenkins` to `jenkins/jenkins` seamlessly by ensuring you have the latest [repository info](#get-repository-info) and running the [upgrade commands](#upgrade-chart) specifying the `jenkins/jenkins` chart. + +### Major Version Upgrades + +Chart release versions follow [SemVer](../../CONTRIBUTING.md#versioning), where a MAJOR version change (example `1.0.0` -> `2.0.0`) indicates an incompatible breaking change needing manual actions. + +See [UPGRADING.md](./UPGRADING.md) for a list of breaking changes diff --git a/charts/jenkins/jenkins/5.5.12/UPGRADING.md b/charts/jenkins/jenkins/5.5.12/UPGRADING.md new file mode 100644 index 0000000000..0ff90112d6 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/UPGRADING.md @@ -0,0 +1,148 @@ +# Upgrade Notes + +## To 5.0.0 +- `controller.image`, `controller.tag`, and `controller.tagLabel` have been removed. If you want to overwrite the image you now need to configure any or all of: + - `controller.image.registry` + - `controller.image.repository` + - `controller.image.tag` + - `controller.image.tagLabel` +- `controller.imagePullPolicy` has been removed. If you want to overwrite the pull policy you now need to configure `controller.image.pullPolicy`. +- `controller.sidecars.configAutoReload.image` has been removed. If you want to overwrite the configAutoReload image you now need to configure any or all of: + - `controller.sidecars.configAutoReload.image.registry` + - `controller.sidecars.configAutoReload.image.repository` + - `controller.sidecars.configAutoReload.image.tag` +- `controller.sidecars.other` has been renamed to `controller.sidecars.additionalSidecarContainers`. +- `agent.image` and `agent.tag` have been removed. If you want to overwrite the agent image you now need to configure any or all of: + - `agent.image.repository` + - `agent.image.tag` + - The registry can still be overwritten by `agent.jnlpregistry` +- `agent.additionalContainers[*].image` has been renamed to `agent.additionalContainers[*].image.repository` +- `agent.additionalContainers[*].tag` has been renamed to `agent.additionalContainers[*].image.tag` +- `additionalAgents.*.image` has been renamed to `additionalAgents.*.image.repository` +- `additionalAgents.*.tag` has been renamed to `additionalAgents.*.image.tag` +- `additionalClouds.*.additionalAgents.*.image` has been renamed to `additionalClouds.*.additionalAgents.*.image.repository` +- `additionalClouds.*.additionalAgents.*.tag` has been renamed to `additionalClouds.*.additionalAgents.*.image.tag` +- `helmtest.bats.image` has been split up to: + - `helmtest.bats.image.registry` + - `helmtest.bats.image.repository` + - `helmtest.bats.image.tag` +- `controller.adminUsername` and `controller.adminPassword` have been renamed to `controller.admin.username` and `controller.admin.password` respectively +- `controller.adminSecret` has been renamed to `controller.admin.createSecret` +- `backup.*` was unmaintained and has thus been removed. See the following page for alternatives: [Kubernetes Backup and Migrations](https://nubenetes.com/kubernetes-backup-migrations/). + +## To 4.0.0 +Removes automatic `remotingSecurity` setting when using a container tag older than `2.326` (introduced in [`3.11.7`](./CHANGELOG.md#3117)). If you're using a version older than `2.326`, you should explicitly set `.controller.legacyRemotingSecurityEnabled` to `true`. + +## To 3.0.0 + +* Check `securityRealm` and `authorizationStrategy` and adjust it. + Otherwise, your configured users and permissions will be overridden. +* You need to use helm version 3 as the `Chart.yaml` uses `apiVersion: v2`. +* All XML configuration options have been removed. + In case those are still in use you need to migrate to configuration as code. + Upgrade guide to 2.0.0 contains pointers how to do that. +* Jenkins is now using a `StatefulSet` instead of a `Deployment` +* terminology has been adjusted that's also reflected in values.yaml + The following values from `values.yaml` have been renamed: + + * `master` => `controller` + * `master.useSecurity` => `controller.adminSecret` + * `master.slaveListenerPort` => `controller.agentListenerPort` + * `master.slaveHostPort` => `controller.agentListenerHostPort` + * `master.slaveKubernetesNamespace` => `agent.namespace` + * `master.slaveDefaultsProviderTemplate` => `agent.defaultsProviderTemplate` + * `master.slaveJenkinsUrl` => `agent.jenkinsUrl` + * `master.slaveJenkinsTunnel` => `agent.jenkinsTunnel` + * `master.slaveConnectTimeout` => `agent.kubernetesConnectTimeout` + * `master.slaveReadTimeout` => `agent.kubernetesReadTimeout` + * `master.slaveListenerServiceAnnotations` => `controller.agentListenerServiceAnnotations` + * `master.slaveListenerServiceType` => `controller.agentListenerServiceType` + * `master.slaveListenerLoadBalancerIP` => `controller.agentListenerLoadBalancerIP` + * `agent.slaveConnectTimeout` => `agent.connectTimeout` +* Removed values: + + * `master.imageTag`: use `controller.image` and `controller.tag` instead + * `slave.imageTag`: use `agent.image` and `agent.tag` instead + +## To 2.0.0 + +Configuration as Code is now default + container does not run as root anymore. + +### Configuration as Code new default + +Configuration is done via [Jenkins Configuration as Code Plugin](https://github.com/jenkinsci/configuration-as-code-plugin) by default. +That means that changes in values which result in a configuration change are always applied. +In contrast, the XML configuration was only applied during the first start and never altered. + +:exclamation::exclamation::exclamation: +Attention: +This also means if you manually altered configuration then this will most likely be reset to what was configured by default. +It also applies to `securityRealm` and `authorizationStrategy` as they are also configured using configuration as code. +:exclamation::exclamation::exclamation: + +### Image does not run as root anymore + +It's not recommended to run containers in Kubernetes as `root`. + +❗Attention: If you had not configured a different user before then you need to ensure that your image supports the user and group ID configured and also manually change permissions of all files so that Jenkins is still able to use them. + +### Summary of updated values + +As version 2.0.0 only updates default values and nothing else it's still possible to migrate to this version and opt out of some or all new defaults. +All you have to do is ensure the old values are set in your installation. + +Here we show which values have changed and the previous default values: + +```yaml +controller: + runAsUser: 1000 # was unset before + fsGroup: 1000 # was unset before + JCasC: + enabled: true # was false + defaultConfig: true # was false + sidecars: + configAutoReload: + enabled: true # was false +``` + +### Migration steps + +Migration instructions heavily depend on your current setup. +So think of the list below more as a general guideline of what should be done. + +- Ensure that the Jenkins image you are using contains a user with ID 1000 and a group with the same ID. + That's the case for `jenkins/jenkins:lts` image, which the chart uses by default +- Make a backup of your existing installation especially the persistent volume +- Ensure that you have the configuration as code plugin installed +- Export your current settings via the plugin: + `Manage Jenkins` -> `Configuration as Code` -> `Download Configuration` +- prepare your values file for the update e.g. add additional configuration as code setting that you need. + The export taken from above might be a good starting point for this. + In addition, the [demos](https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos) from the plugin itself are quite useful. +- Test drive those setting on a separate installation +- Put Jenkins to Quiet Down mode so that it does not accept new jobs + `/quietDown` +- Change permissions of all files and folders to the new user and group ID: + + ```console + kubectl exec -it -c jenkins /bin/bash + chown -R 1000:1000 /var/jenkins_home + ``` + +- Update Jenkins + +## To 1.0.0 + +Breaking changes: + +- Values have been renamed to follow [helm recommended naming conventions](https://helm.sh/docs/chart_best_practices/#naming-conventions) so that all variables start with a lowercase letter and words are separated with camelcase +- All resources are now using [helm recommended standard labels](https://helm.sh/docs/chart_best_practices/#standard-labels) + +As a result of the label changes also the selectors of the deployment have been updated. +Those are immutable so trying an updated will cause an error like: + +```console +Error: Deployment.apps "jenkins" is invalid: spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{"app.kubernetes.io/component":"jenkins-controller", "app.kubernetes.io/instance":"jenkins"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: field is immutable +``` + +In order to upgrade, [uninstall](./README.md#uninstall-chart) the Jenkins Deployment before upgrading: diff --git a/charts/jenkins/jenkins/5.5.12/VALUES.md b/charts/jenkins/jenkins/5.5.12/VALUES.md new file mode 100644 index 0000000000..7054f431eb --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/VALUES.md @@ -0,0 +1,316 @@ +# Jenkins + +## Configuration + +The following tables list the configurable parameters of the Jenkins chart and their default values. + +## Values + +| Key | Type | Description | Default | +|:----|:-----|:---------|:------------| +| [additionalAgents](./values.yaml#L1189) | object | Configure additional | `{}` | +| [additionalClouds](./values.yaml#L1214) | object | | `{}` | +| [agent.TTYEnabled](./values.yaml#L1095) | bool | Allocate pseudo tty to the side container | `false` | +| [agent.additionalContainers](./values.yaml#L1142) | list | Add additional containers to the agents | `[]` | +| [agent.alwaysPullImage](./values.yaml#L988) | bool | Always pull agent container image before build | `false` | +| [agent.annotations](./values.yaml#L1138) | object | Annotations to apply to the pod | `{}` | +| [agent.args](./values.yaml#L1089) | string | Arguments passed to command to execute | `"${computer.jnlpmac} ${computer.name}"` | +| [agent.command](./values.yaml#L1087) | string | Command to execute when side container starts | `nil` | +| [agent.componentName](./values.yaml#L956) | string | | `"jenkins-agent"` | +| [agent.connectTimeout](./values.yaml#L1136) | int | Timeout in seconds for an agent to be online | `100` | +| [agent.containerCap](./values.yaml#L1097) | int | Max number of agents to launch | `10` | +| [agent.customJenkinsLabels](./values.yaml#L953) | list | Append Jenkins labels to the agent | `[]` | +| [agent.defaultsProviderTemplate](./values.yaml#L907) | string | The name of the pod template to use for providing default values | `""` | +| [agent.directConnection](./values.yaml#L959) | bool | | `false` | +| [agent.disableDefaultAgent](./values.yaml#L1160) | bool | Disable the default Jenkins Agent configuration | `false` | +| [agent.enabled](./values.yaml#L905) | bool | Enable Kubernetes plugin jnlp-agent podTemplate | `true` | +| [agent.envVars](./values.yaml#L1070) | list | Environment variables for the agent Pod | `[]` | +| [agent.garbageCollection.enabled](./values.yaml#L1104) | bool | When enabled, Jenkins will periodically check for orphan pods that have not been touched for the given timeout period and delete them. | `false` | +| [agent.garbageCollection.namespaces](./values.yaml#L1106) | string | Namespaces to look at for garbage collection, in addition to the default namespace defined for the cloud. One namespace per line. | `""` | +| [agent.garbageCollection.timeout](./values.yaml#L1111) | int | Timeout value for orphaned pods | `300` | +| [agent.hostNetworking](./values.yaml#L967) | bool | Enables the agent to use the host network | `false` | +| [agent.idleMinutes](./values.yaml#L1114) | int | Allows the Pod to remain active for reuse until the configured number of minutes has passed since the last step was executed on it | `0` | +| [agent.image.repository](./values.yaml#L946) | string | Repository to pull the agent jnlp image from | `"jenkins/inbound-agent"` | +| [agent.image.tag](./values.yaml#L948) | string | Tag of the image to pull | `"3261.v9c670a_4748a_9-1"` | +| [agent.imagePullSecretName](./values.yaml#L955) | string | Name of the secret to be used to pull the image | `nil` | +| [agent.inheritYamlMergeStrategy](./values.yaml#L1134) | bool | Controls whether the defined yaml merge strategy will be inherited if another defined pod template is configured to inherit from the current one | `false` | +| [agent.jenkinsTunnel](./values.yaml#L923) | string | Overrides the Kubernetes Jenkins tunnel | `nil` | +| [agent.jenkinsUrl](./values.yaml#L919) | string | Overrides the Kubernetes Jenkins URL | `nil` | +| [agent.jnlpregistry](./values.yaml#L943) | string | Custom registry used to pull the agent jnlp image from | `nil` | +| [agent.kubernetesConnectTimeout](./values.yaml#L929) | int | The connection timeout in seconds for connections to Kubernetes API. The minimum value is 5 | `5` | +| [agent.kubernetesReadTimeout](./values.yaml#L931) | int | The read timeout in seconds for connections to Kubernetes API. The minimum value is 15 | `15` | +| [agent.livenessProbe](./values.yaml#L978) | object | | `{}` | +| [agent.maxRequestsPerHostStr](./values.yaml#L933) | string | The maximum concurrent connections to Kubernetes API | `"32"` | +| [agent.namespace](./values.yaml#L939) | string | Namespace in which the Kubernetes agents should be launched | `nil` | +| [agent.nodeSelector](./values.yaml#L1081) | object | Node labels for pod assignment | `{}` | +| [agent.nodeUsageMode](./values.yaml#L951) | string | | `"NORMAL"` | +| [agent.podLabels](./values.yaml#L941) | object | Custom Pod labels (an object with `label-key: label-value` pairs) | `{}` | +| [agent.podName](./values.yaml#L1099) | string | Agent Pod base name | `"default"` | +| [agent.podRetention](./values.yaml#L997) | string | | `"Never"` | +| [agent.podTemplates](./values.yaml#L1170) | object | Configures extra pod templates for the default kubernetes cloud | `{}` | +| [agent.privileged](./values.yaml#L961) | bool | Agent privileged container | `false` | +| [agent.resources](./values.yaml#L969) | object | Resources allocation (Requests and Limits) | `{"limits":{"cpu":"512m","memory":"512Mi"},"requests":{"cpu":"512m","memory":"512Mi"}}` | +| [agent.restrictedPssSecurityContext](./values.yaml#L994) | bool | Set a restricted securityContext on jnlp containers | `false` | +| [agent.retentionTimeout](./values.yaml#L935) | int | Time in minutes after which the Kubernetes cloud plugin will clean up an idle worker that has not already terminated | `5` | +| [agent.runAsGroup](./values.yaml#L965) | string | Configure container group | `nil` | +| [agent.runAsUser](./values.yaml#L963) | string | Configure container user | `nil` | +| [agent.secretEnvVars](./values.yaml#L1074) | list | Mount a secret as environment variable | `[]` | +| [agent.serviceAccount](./values.yaml#L915) | string | Override the default service account | `serviceAccountAgent.name` if `agent.useDefaultServiceAccount` is `true` | +| [agent.showRawYaml](./values.yaml#L1001) | bool | | `true` | +| [agent.sideContainerName](./values.yaml#L1091) | string | Side container name | `"jnlp"` | +| [agent.skipTlsVerify](./values.yaml#L925) | bool | Disables the verification of the controller certificate on remote connection. This flag correspond to the "Disable https certificate check" flag in kubernetes plugin UI | `false` | +| [agent.usageRestricted](./values.yaml#L927) | bool | Enable the possibility to restrict the usage of this agent to specific folder. This flag correspond to the "Restrict pipeline support to authorized folders" flag in kubernetes plugin UI | `false` | +| [agent.useDefaultServiceAccount](./values.yaml#L911) | bool | Use `serviceAccountAgent.name` as the default value for defaults template `serviceAccount` | `true` | +| [agent.volumes](./values.yaml#L1008) | list | Additional volumes | `[]` | +| [agent.waitForPodSec](./values.yaml#L937) | int | Seconds to wait for pod to be running | `600` | +| [agent.websocket](./values.yaml#L958) | bool | Enables agent communication via websockets | `false` | +| [agent.workingDir](./values.yaml#L950) | string | Configure working directory for default agent | `"/home/jenkins/agent"` | +| [agent.workspaceVolume](./values.yaml#L1043) | object | Workspace volume (defaults to EmptyDir) | `{}` | +| [agent.yamlMergeStrategy](./values.yaml#L1132) | string | Defines how the raw yaml field gets merged with yaml definitions from inherited pod templates. Possible values: "merge" or "override" | `"override"` | +| [agent.yamlTemplate](./values.yaml#L1121) | string | The raw yaml of a Pod API Object to merge into the agent spec | `""` | +| [awsSecurityGroupPolicies.enabled](./values.yaml#L1340) | bool | | `false` | +| [awsSecurityGroupPolicies.policies[0].name](./values.yaml#L1342) | string | | `""` | +| [awsSecurityGroupPolicies.policies[0].podSelector](./values.yaml#L1344) | object | | `{}` | +| [awsSecurityGroupPolicies.policies[0].securityGroupIds](./values.yaml#L1343) | list | | `[]` | +| [checkDeprecation](./values.yaml#L1337) | bool | Checks if any deprecated values are used | `true` | +| [clusterZone](./values.yaml#L21) | string | Override the cluster name for FQDN resolving | `"cluster.local"` | +| [controller.JCasC.authorizationStrategy](./values.yaml#L533) | string | Jenkins Config as Code Authorization Strategy-section | `"loggedInUsersCanDoAnything:\n allowAnonymousRead: false"` | +| [controller.JCasC.configMapAnnotations](./values.yaml#L538) | object | Annotations for the JCasC ConfigMap | `{}` | +| [controller.JCasC.configScripts](./values.yaml#L507) | object | List of Jenkins Config as Code scripts | `{}` | +| [controller.JCasC.configUrls](./values.yaml#L504) | list | Remote URLs for configuration files. | `[]` | +| [controller.JCasC.defaultConfig](./values.yaml#L498) | bool | Enables default Jenkins configuration via configuration as code plugin | `true` | +| [controller.JCasC.overwriteConfiguration](./values.yaml#L502) | bool | Whether Jenkins Config as Code should overwrite any existing configuration | `false` | +| [controller.JCasC.security](./values.yaml#L514) | object | Jenkins Config as Code security-section | `{"apiToken":{"creationOfLegacyTokenEnabled":false,"tokenGenerationOnCreationEnabled":false,"usageStatisticsEnabled":true}}` | +| [controller.JCasC.securityRealm](./values.yaml#L522) | string | Jenkins Config as Code Security Realm-section | `"local:\n allowsSignup: false\n enableCaptcha: false\n users:\n - id: \"${chart-admin-username}\"\n name: \"Jenkins Admin\"\n password: \"${chart-admin-password}\""` | +| [controller.additionalExistingSecrets](./values.yaml#L459) | list | List of additional existing secrets to mount | `[]` | +| [controller.additionalPlugins](./values.yaml#L409) | list | List of plugins to install in addition to those listed in controller.installPlugins | `[]` | +| [controller.additionalSecrets](./values.yaml#L468) | list | List of additional secrets to create and mount | `[]` | +| [controller.admin.createSecret](./values.yaml#L91) | bool | Create secret for admin user | `true` | +| [controller.admin.existingSecret](./values.yaml#L94) | string | The name of an existing secret containing the admin credentials | `""` | +| [controller.admin.password](./values.yaml#L81) | string | Admin password created as a secret if `controller.admin.createSecret` is true | `` | +| [controller.admin.passwordKey](./values.yaml#L86) | string | The key in the existing admin secret containing the password | `"jenkins-admin-password"` | +| [controller.admin.userKey](./values.yaml#L84) | string | The key in the existing admin secret containing the username | `"jenkins-admin-user"` | +| [controller.admin.username](./values.yaml#L78) | string | Admin username created as a secret if `controller.admin.createSecret` is true | `"admin"` | +| [controller.affinity](./values.yaml#L660) | object | Affinity settings | `{}` | +| [controller.agentListenerEnabled](./values.yaml#L318) | bool | Create Agent listener service | `true` | +| [controller.agentListenerExternalTrafficPolicy](./values.yaml#L328) | string | Traffic Policy of for the agentListener service | `nil` | +| [controller.agentListenerHostPort](./values.yaml#L322) | string | Host port to listen for agents | `nil` | +| [controller.agentListenerLoadBalancerIP](./values.yaml#L358) | string | Static IP for the agentListener LoadBalancer | `nil` | +| [controller.agentListenerLoadBalancerSourceRanges](./values.yaml#L330) | list | Allowed inbound IP for the agentListener service | `["0.0.0.0/0"]` | +| [controller.agentListenerNodePort](./values.yaml#L324) | string | Node port to listen for agents | `nil` | +| [controller.agentListenerPort](./values.yaml#L320) | int | Listening port for agents | `50000` | +| [controller.agentListenerServiceAnnotations](./values.yaml#L353) | object | Annotations for the agentListener service | `{}` | +| [controller.agentListenerServiceType](./values.yaml#L350) | string | Defines how to expose the agentListener service | `"ClusterIP"` | +| [controller.backendconfig.annotations](./values.yaml#L763) | object | backendconfig annotations | `{}` | +| [controller.backendconfig.apiVersion](./values.yaml#L757) | string | backendconfig API version | `"extensions/v1beta1"` | +| [controller.backendconfig.enabled](./values.yaml#L755) | bool | Enables backendconfig | `false` | +| [controller.backendconfig.labels](./values.yaml#L761) | object | backendconfig labels | `{}` | +| [controller.backendconfig.name](./values.yaml#L759) | string | backendconfig name | `nil` | +| [controller.backendconfig.spec](./values.yaml#L765) | object | backendconfig spec | `{}` | +| [controller.cloudName](./values.yaml#L487) | string | Name of default cloud configuration. | `"kubernetes"` | +| [controller.clusterIp](./values.yaml#L217) | string | k8s service clusterIP. Only used if serviceType is ClusterIP | `nil` | +| [controller.componentName](./values.yaml#L34) | string | Used for label app.kubernetes.io/component | `"jenkins-controller"` | +| [controller.containerEnv](./values.yaml#L150) | list | Environment variables for Jenkins Container | `[]` | +| [controller.containerEnvFrom](./values.yaml#L147) | list | Environment variable sources for Jenkins Container | `[]` | +| [controller.containerSecurityContext](./values.yaml#L205) | object | Allow controlling the securityContext for the jenkins container | `{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsUser":1000}` | +| [controller.csrf.defaultCrumbIssuer.enabled](./values.yaml#L339) | bool | Enable the default CSRF Crumb issuer | `true` | +| [controller.csrf.defaultCrumbIssuer.proxyCompatability](./values.yaml#L341) | bool | Enable proxy compatibility | `true` | +| [controller.customInitContainers](./values.yaml#L541) | list | Custom init-container specification in raw-yaml format | `[]` | +| [controller.customJenkinsLabels](./values.yaml#L68) | list | Append Jenkins labels to the controller | `[]` | +| [controller.disableRememberMe](./values.yaml#L59) | bool | Disable use of remember me | `false` | +| [controller.disabledAgentProtocols](./values.yaml#L333) | list | Disabled agent protocols | `["JNLP-connect","JNLP2-connect"]` | +| [controller.enableRawHtmlMarkupFormatter](./values.yaml#L429) | bool | Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter) | `false` | +| [controller.executorMode](./values.yaml#L65) | string | Sets the executor mode of the Jenkins node. Possible values are "NORMAL" or "EXCLUSIVE" | `"NORMAL"` | +| [controller.existingSecret](./values.yaml#L456) | string | | `nil` | +| [controller.extraPorts](./values.yaml#L388) | list | Optionally configure other ports to expose in the controller container | `[]` | +| [controller.fsGroup](./values.yaml#L186) | int | Deprecated in favor of `controller.podSecurityContextOverride`. uid that will be used for persistent volume. | `1000` | +| [controller.googlePodMonitor.enabled](./values.yaml#L826) | bool | | `false` | +| [controller.googlePodMonitor.scrapeEndpoint](./values.yaml#L831) | string | | `"/prometheus"` | +| [controller.googlePodMonitor.scrapeInterval](./values.yaml#L829) | string | | `"60s"` | +| [controller.healthProbes](./values.yaml#L248) | bool | Enable Kubernetes Probes configuration configured in `controller.probes` | `true` | +| [controller.hostAliases](./values.yaml#L779) | list | Allows for adding entries to Pod /etc/hosts | `[]` | +| [controller.hostNetworking](./values.yaml#L70) | bool | | `false` | +| [controller.httpsKeyStore.disableSecretMount](./values.yaml#L847) | bool | | `false` | +| [controller.httpsKeyStore.enable](./values.yaml#L838) | bool | Enables HTTPS keystore on jenkins controller | `false` | +| [controller.httpsKeyStore.fileName](./values.yaml#L855) | string | Jenkins keystore filename which will appear under controller.httpsKeyStore.path | `"keystore.jks"` | +| [controller.httpsKeyStore.httpPort](./values.yaml#L851) | int | HTTP Port that Jenkins should listen to along with HTTPS, it also serves as the liveness and readiness probes port. | `8081` | +| [controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey](./values.yaml#L846) | string | Name of the key in the secret that contains the JKS password | `"https-jks-password"` | +| [controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName](./values.yaml#L844) | string | Name of the secret that contains the JKS password, if it is not in the same secret as the JKS file | `""` | +| [controller.httpsKeyStore.jenkinsHttpsJksSecretKey](./values.yaml#L842) | string | Name of the key in the secret that already has ssl keystore | `"jenkins-jks-file"` | +| [controller.httpsKeyStore.jenkinsHttpsJksSecretName](./values.yaml#L840) | string | Name of the secret that already has ssl keystore | `""` | +| [controller.httpsKeyStore.jenkinsKeyStoreBase64Encoded](./values.yaml#L860) | string | Base64 encoded Keystore content. Keystore must be converted to base64 then being pasted here | `nil` | +| [controller.httpsKeyStore.password](./values.yaml#L857) | string | Jenkins keystore password | `"password"` | +| [controller.httpsKeyStore.path](./values.yaml#L853) | string | Path of HTTPS keystore file | `"/var/jenkins_keystore"` | +| [controller.image.pullPolicy](./values.yaml#L47) | string | Controller image pull policy | `"Always"` | +| [controller.image.registry](./values.yaml#L37) | string | Controller image registry | `"docker.io"` | +| [controller.image.repository](./values.yaml#L39) | string | Controller image repository | `"jenkins/jenkins"` | +| [controller.image.tag](./values.yaml#L42) | string | Controller image tag override; i.e., tag: "2.440.1-jdk17" | `nil` | +| [controller.image.tagLabel](./values.yaml#L45) | string | Controller image tag label | `"jdk17"` | +| [controller.imagePullSecretName](./values.yaml#L49) | string | Controller image pull secret | `nil` | +| [controller.ingress.annotations](./values.yaml#L702) | object | Ingress annotations | `{}` | +| [controller.ingress.apiVersion](./values.yaml#L698) | string | Ingress API version | `"extensions/v1beta1"` | +| [controller.ingress.enabled](./values.yaml#L681) | bool | Enables ingress | `false` | +| [controller.ingress.hostName](./values.yaml#L715) | string | Ingress hostname | `nil` | +| [controller.ingress.labels](./values.yaml#L700) | object | Ingress labels | `{}` | +| [controller.ingress.path](./values.yaml#L711) | string | Ingress path | `nil` | +| [controller.ingress.paths](./values.yaml#L685) | list | Override for the default Ingress paths | `[]` | +| [controller.ingress.resourceRootUrl](./values.yaml#L717) | string | Hostname to serve assets from | `nil` | +| [controller.ingress.tls](./values.yaml#L719) | list | Ingress TLS configuration | `[]` | +| [controller.initConfigMap](./values.yaml#L446) | string | Name of the existing ConfigMap that contains init scripts | `nil` | +| [controller.initContainerEnv](./values.yaml#L141) | list | Environment variables for Init Container | `[]` | +| [controller.initContainerEnvFrom](./values.yaml#L137) | list | Environment variable sources for Init Container | `[]` | +| [controller.initContainerResources](./values.yaml#L128) | object | Resources allocation (Requests and Limits) for Init Container | `{}` | +| [controller.initScripts](./values.yaml#L442) | object | Map of groovy init scripts to be executed during Jenkins controller start | `{}` | +| [controller.initializeOnce](./values.yaml#L414) | bool | Initialize only on first installation. Ensures plugins do not get updated inadvertently. Requires `persistence.enabled` to be set to `true` | `false` | +| [controller.installLatestPlugins](./values.yaml#L403) | bool | Download the minimum required version or latest version of all dependencies | `true` | +| [controller.installLatestSpecifiedPlugins](./values.yaml#L406) | bool | Set to true to download the latest version of any plugin that is requested to have the latest version | `false` | +| [controller.installPlugins](./values.yaml#L395) | list | List of Jenkins plugins to install. If you don't want to install plugins, set it to `false` | `["kubernetes:4285.v50ed5f624918","workflow-aggregator:600.vb_57cdd26fdd7","git:5.4.1","configuration-as-code:1850.va_a_8c31d3158b_"]` | +| [controller.javaOpts](./values.yaml#L156) | string | Append to `JAVA_OPTS` env var | `nil` | +| [controller.jenkinsAdminEmail](./values.yaml#L96) | string | Email address for the administrator of the Jenkins instance | `nil` | +| [controller.jenkinsHome](./values.yaml#L101) | string | Custom Jenkins home path | `"/var/jenkins_home"` | +| [controller.jenkinsOpts](./values.yaml#L158) | string | Append to `JENKINS_OPTS` env var | `nil` | +| [controller.jenkinsRef](./values.yaml#L106) | string | Custom Jenkins reference path | `"/usr/share/jenkins/ref"` | +| [controller.jenkinsUriPrefix](./values.yaml#L173) | string | Root URI Jenkins will be served on | `nil` | +| [controller.jenkinsUrl](./values.yaml#L168) | string | Set Jenkins URL if you are not using the ingress definitions provided by the chart | `nil` | +| [controller.jenkinsUrlProtocol](./values.yaml#L165) | string | Set protocol for Jenkins URL; `https` if `controller.ingress.tls`, `http` otherwise | `nil` | +| [controller.jenkinsWar](./values.yaml#L109) | string | | `"/usr/share/jenkins/jenkins.war"` | +| [controller.jmxPort](./values.yaml#L385) | string | Open a port, for JMX stats | `nil` | +| [controller.legacyRemotingSecurityEnabled](./values.yaml#L361) | bool | Whether legacy remoting security should be enabled | `false` | +| [controller.lifecycle](./values.yaml#L51) | object | Lifecycle specification for controller-container | `{}` | +| [controller.loadBalancerIP](./values.yaml#L376) | string | Optionally assign a known public LB IP | `nil` | +| [controller.loadBalancerSourceRanges](./values.yaml#L372) | list | Allowed inbound IP addresses | `["0.0.0.0/0"]` | +| [controller.markupFormatter](./values.yaml#L433) | string | Yaml of the markup formatter to use | `"plainText"` | +| [controller.nodePort](./values.yaml#L223) | string | k8s node port. Only used if serviceType is NodePort | `nil` | +| [controller.nodeSelector](./values.yaml#L647) | object | Node labels for pod assignment | `{}` | +| [controller.numExecutors](./values.yaml#L62) | int | Set Number of executors | `0` | +| [controller.overwritePlugins](./values.yaml#L418) | bool | Overwrite installed plugins on start | `false` | +| [controller.overwritePluginsFromImage](./values.yaml#L422) | bool | Overwrite plugins that are already installed in the controller image | `true` | +| [controller.podAnnotations](./values.yaml#L668) | object | Annotations for controller pod | `{}` | +| [controller.podDisruptionBudget.annotations](./values.yaml#L312) | object | | `{}` | +| [controller.podDisruptionBudget.apiVersion](./values.yaml#L310) | string | Policy API version | `"policy/v1beta1"` | +| [controller.podDisruptionBudget.enabled](./values.yaml#L305) | bool | Enable Kubernetes Pod Disruption Budget configuration | `false` | +| [controller.podDisruptionBudget.labels](./values.yaml#L313) | object | | `{}` | +| [controller.podDisruptionBudget.maxUnavailable](./values.yaml#L315) | string | Number of pods that can be unavailable. Either an absolute number or a percentage | `"0"` | +| [controller.podLabels](./values.yaml#L241) | object | Custom Pod labels (an object with `label-key: label-value` pairs) | `{}` | +| [controller.podSecurityContextOverride](./values.yaml#L202) | string | Completely overwrites the contents of the pod security context, ignoring the values provided for `runAsUser`, `fsGroup`, and `securityContextCapabilities` | `nil` | +| [controller.priorityClassName](./values.yaml#L665) | string | The name of a `priorityClass` to apply to the controller pod | `nil` | +| [controller.probes.livenessProbe.failureThreshold](./values.yaml#L266) | int | Set the failure threshold for the liveness probe | `5` | +| [controller.probes.livenessProbe.httpGet.path](./values.yaml#L269) | string | Set the Pod's HTTP path for the liveness probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.livenessProbe.httpGet.port](./values.yaml#L271) | string | Set the Pod's HTTP port to use for the liveness probe | `"http"` | +| [controller.probes.livenessProbe.initialDelaySeconds](./values.yaml#L280) | string | Set the initial delay for the liveness probe in seconds | `nil` | +| [controller.probes.livenessProbe.periodSeconds](./values.yaml#L273) | int | Set the time interval between two liveness probes executions in seconds | `10` | +| [controller.probes.livenessProbe.timeoutSeconds](./values.yaml#L275) | int | Set the timeout for the liveness probe in seconds | `5` | +| [controller.probes.readinessProbe.failureThreshold](./values.yaml#L284) | int | Set the failure threshold for the readiness probe | `3` | +| [controller.probes.readinessProbe.httpGet.path](./values.yaml#L287) | string | Set the Pod's HTTP path for the liveness probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.readinessProbe.httpGet.port](./values.yaml#L289) | string | Set the Pod's HTTP port to use for the readiness probe | `"http"` | +| [controller.probes.readinessProbe.initialDelaySeconds](./values.yaml#L298) | string | Set the initial delay for the readiness probe in seconds | `nil` | +| [controller.probes.readinessProbe.periodSeconds](./values.yaml#L291) | int | Set the time interval between two readiness probes executions in seconds | `10` | +| [controller.probes.readinessProbe.timeoutSeconds](./values.yaml#L293) | int | Set the timeout for the readiness probe in seconds | `5` | +| [controller.probes.startupProbe.failureThreshold](./values.yaml#L253) | int | Set the failure threshold for the startup probe | `12` | +| [controller.probes.startupProbe.httpGet.path](./values.yaml#L256) | string | Set the Pod's HTTP path for the startup probe | `"{{ default \"\" .Values.controller.jenkinsUriPrefix }}/login"` | +| [controller.probes.startupProbe.httpGet.port](./values.yaml#L258) | string | Set the Pod's HTTP port to use for the startup probe | `"http"` | +| [controller.probes.startupProbe.periodSeconds](./values.yaml#L260) | int | Set the time interval between two startup probes executions in seconds | `10` | +| [controller.probes.startupProbe.timeoutSeconds](./values.yaml#L262) | int | Set the timeout for the startup probe in seconds | `5` | +| [controller.projectNamingStrategy](./values.yaml#L425) | string | | `"standard"` | +| [controller.prometheus.alertingRulesAdditionalLabels](./values.yaml#L812) | object | Additional labels to add to the PrometheusRule object | `{}` | +| [controller.prometheus.alertingrules](./values.yaml#L810) | list | Array of prometheus alerting rules | `[]` | +| [controller.prometheus.enabled](./values.yaml#L795) | bool | Enables prometheus service monitor | `false` | +| [controller.prometheus.metricRelabelings](./values.yaml#L822) | list | | `[]` | +| [controller.prometheus.prometheusRuleNamespace](./values.yaml#L814) | string | Set a custom namespace where to deploy PrometheusRule resource | `""` | +| [controller.prometheus.relabelings](./values.yaml#L820) | list | | `[]` | +| [controller.prometheus.scrapeEndpoint](./values.yaml#L805) | string | The endpoint prometheus should get metrics from | `"/prometheus"` | +| [controller.prometheus.scrapeInterval](./values.yaml#L801) | string | How often prometheus should scrape metrics | `"60s"` | +| [controller.prometheus.serviceMonitorAdditionalLabels](./values.yaml#L797) | object | Additional labels to add to the service monitor object | `{}` | +| [controller.prometheus.serviceMonitorNamespace](./values.yaml#L799) | string | Set a custom namespace where to deploy ServiceMonitor resource | `nil` | +| [controller.resources](./values.yaml#L115) | object | Resource allocation (Requests and Limits) | `{"limits":{"cpu":"2000m","memory":"4096Mi"},"requests":{"cpu":"50m","memory":"256Mi"}}` | +| [controller.route.annotations](./values.yaml#L774) | object | Route annotations | `{}` | +| [controller.route.enabled](./values.yaml#L770) | bool | Enables openshift route | `false` | +| [controller.route.labels](./values.yaml#L772) | object | Route labels | `{}` | +| [controller.route.path](./values.yaml#L776) | string | Route path | `nil` | +| [controller.runAsUser](./values.yaml#L183) | int | Deprecated in favor of `controller.podSecurityContextOverride`. uid that jenkins runs with. | `1000` | +| [controller.schedulerName](./values.yaml#L643) | string | Name of the Kubernetes scheduler to use | `""` | +| [controller.scriptApproval](./values.yaml#L437) | list | List of groovy functions to approve | `[]` | +| [controller.secondaryingress.annotations](./values.yaml#L737) | object | | `{}` | +| [controller.secondaryingress.apiVersion](./values.yaml#L735) | string | | `"extensions/v1beta1"` | +| [controller.secondaryingress.enabled](./values.yaml#L729) | bool | | `false` | +| [controller.secondaryingress.hostName](./values.yaml#L744) | string | | `nil` | +| [controller.secondaryingress.labels](./values.yaml#L736) | object | | `{}` | +| [controller.secondaryingress.paths](./values.yaml#L732) | list | | `[]` | +| [controller.secondaryingress.tls](./values.yaml#L745) | string | | `nil` | +| [controller.secretClaims](./values.yaml#L480) | list | List of `SecretClaim` resources to create | `[]` | +| [controller.securityContextCapabilities](./values.yaml#L192) | object | | `{}` | +| [controller.serviceAnnotations](./values.yaml#L230) | object | Jenkins controller service annotations | `{}` | +| [controller.serviceExternalTrafficPolicy](./values.yaml#L227) | string | | `nil` | +| [controller.serviceLabels](./values.yaml#L236) | object | Labels for the Jenkins controller-service | `{}` | +| [controller.servicePort](./values.yaml#L219) | int | k8s service port | `8080` | +| [controller.serviceType](./values.yaml#L214) | string | k8s service type | `"ClusterIP"` | +| [controller.shareProcessNamespace](./values.yaml#L124) | bool | | `false` | +| [controller.sidecars.additionalSidecarContainers](./values.yaml#L625) | list | Configures additional sidecar container(s) for the Jenkins controller | `[]` | +| [controller.sidecars.configAutoReload.additionalVolumeMounts](./values.yaml#L571) | list | Enables additional volume mounts for the config auto-reload container | `[]` | +| [controller.sidecars.configAutoReload.containerSecurityContext](./values.yaml#L620) | object | Enable container security context | `{"allowPrivilegeEscalation":false,"readOnlyRootFilesystem":true}` | +| [controller.sidecars.configAutoReload.enabled](./values.yaml#L554) | bool | Enables Jenkins Config as Code auto-reload | `true` | +| [controller.sidecars.configAutoReload.env](./values.yaml#L602) | object | Environment variables for the Jenkins Config as Code auto-reload container | `{}` | +| [controller.sidecars.configAutoReload.envFrom](./values.yaml#L600) | list | Environment variable sources for the Jenkins Config as Code auto-reload container | `[]` | +| [controller.sidecars.configAutoReload.folder](./values.yaml#L613) | string | | `"/var/jenkins_home/casc_configs"` | +| [controller.sidecars.configAutoReload.image.registry](./values.yaml#L557) | string | Registry for the image that triggers the reload | `"docker.io"` | +| [controller.sidecars.configAutoReload.image.repository](./values.yaml#L559) | string | Repository of the image that triggers the reload | `"kiwigrid/k8s-sidecar"` | +| [controller.sidecars.configAutoReload.image.tag](./values.yaml#L561) | string | Tag for the image that triggers the reload | `"1.27.5"` | +| [controller.sidecars.configAutoReload.imagePullPolicy](./values.yaml#L562) | string | | `"IfNotPresent"` | +| [controller.sidecars.configAutoReload.logging](./values.yaml#L577) | object | Config auto-reload logging settings | `{"configuration":{"backupCount":3,"formatter":"JSON","logLevel":"INFO","logToConsole":true,"logToFile":false,"maxBytes":1024,"override":false}}` | +| [controller.sidecars.configAutoReload.logging.configuration.override](./values.yaml#L581) | bool | Enables custom log config utilizing using the settings below. | `false` | +| [controller.sidecars.configAutoReload.reqRetryConnect](./values.yaml#L595) | int | How many connection-related errors to retry on | `10` | +| [controller.sidecars.configAutoReload.resources](./values.yaml#L563) | object | | `{}` | +| [controller.sidecars.configAutoReload.scheme](./values.yaml#L590) | string | The scheme to use when connecting to the Jenkins configuration as code endpoint | `"http"` | +| [controller.sidecars.configAutoReload.skipTlsVerify](./values.yaml#L592) | bool | Skip TLS verification when connecting to the Jenkins configuration as code endpoint | `false` | +| [controller.sidecars.configAutoReload.sleepTime](./values.yaml#L597) | string | How many seconds to wait before updating config-maps/secrets (sets METHOD=SLEEP on the sidecar) | `nil` | +| [controller.sidecars.configAutoReload.sshTcpPort](./values.yaml#L611) | int | | `1044` | +| [controller.statefulSetAnnotations](./values.yaml#L670) | object | Annotations for controller StatefulSet | `{}` | +| [controller.statefulSetLabels](./values.yaml#L232) | object | Jenkins controller custom labels for the StatefulSet | `{}` | +| [controller.targetPort](./values.yaml#L221) | int | k8s target port | `8080` | +| [controller.terminationGracePeriodSeconds](./values.yaml#L653) | string | Set TerminationGracePeriodSeconds | `nil` | +| [controller.terminationMessagePath](./values.yaml#L655) | string | Set the termination message path | `nil` | +| [controller.terminationMessagePolicy](./values.yaml#L657) | string | Set the termination message policy | `nil` | +| [controller.testEnabled](./values.yaml#L834) | bool | Can be used to disable rendering controller test resources when using helm template | `true` | +| [controller.tolerations](./values.yaml#L651) | list | Toleration labels for pod assignment | `[]` | +| [controller.topologySpreadConstraints](./values.yaml#L677) | object | Topology spread constraints | `{}` | +| [controller.updateStrategy](./values.yaml#L674) | object | Update strategy for StatefulSet | `{}` | +| [controller.usePodSecurityContext](./values.yaml#L176) | bool | Enable pod security context (must be `true` if podSecurityContextOverride, runAsUser or fsGroup are set) | `true` | +| [credentialsId](./values.yaml#L27) | string | The Jenkins credentials to access the Kubernetes API server. For the default cluster it is not needed. | `nil` | +| [fullnameOverride](./values.yaml#L13) | string | Override the full resource names | `jenkins-(release-name)` or `jenkins` if the release-name is `jenkins` | +| [helmtest.bats.image.registry](./values.yaml#L1353) | string | Registry of the image used to test the framework | `"docker.io"` | +| [helmtest.bats.image.repository](./values.yaml#L1355) | string | Repository of the image used to test the framework | `"bats/bats"` | +| [helmtest.bats.image.tag](./values.yaml#L1357) | string | Tag of the image to test the framework | `"1.11.0"` | +| [kubernetesURL](./values.yaml#L24) | string | The URL of the Kubernetes API server | `"https://kubernetes.default"` | +| [nameOverride](./values.yaml#L10) | string | Override the resource name prefix | `Chart.Name` | +| [namespaceOverride](./values.yaml#L16) | string | Override the deployment namespace | `Release.Namespace` | +| [networkPolicy.apiVersion](./values.yaml#L1283) | string | NetworkPolicy ApiVersion | `"networking.k8s.io/v1"` | +| [networkPolicy.enabled](./values.yaml#L1278) | bool | Enable the creation of NetworkPolicy resources | `false` | +| [networkPolicy.externalAgents.except](./values.yaml#L1297) | list | A list of IP sub-ranges to be excluded from the allowlisted IP range | `[]` | +| [networkPolicy.externalAgents.ipCIDR](./values.yaml#L1295) | string | The IP range from which external agents are allowed to connect to controller, i.e., 172.17.0.0/16 | `nil` | +| [networkPolicy.internalAgents.allowed](./values.yaml#L1287) | bool | Allow internal agents (from the same cluster) to connect to controller. Agent pods will be filtered based on PodLabels | `true` | +| [networkPolicy.internalAgents.namespaceLabels](./values.yaml#L1291) | object | A map of labels (keys/values) that agents namespaces must have to be able to connect to controller | `{}` | +| [networkPolicy.internalAgents.podLabels](./values.yaml#L1289) | object | A map of labels (keys/values) that agent pods must have to be able to connect to controller | `{}` | +| [persistence.accessMode](./values.yaml#L1253) | string | The PVC access mode | `"ReadWriteOnce"` | +| [persistence.annotations](./values.yaml#L1249) | object | Annotations for the PVC | `{}` | +| [persistence.dataSource](./values.yaml#L1259) | object | Existing data source to clone PVC from | `{}` | +| [persistence.enabled](./values.yaml#L1233) | bool | Enable the use of a Jenkins PVC | `true` | +| [persistence.existingClaim](./values.yaml#L1239) | string | Provide the name of a PVC | `nil` | +| [persistence.labels](./values.yaml#L1251) | object | Labels for the PVC | `{}` | +| [persistence.mounts](./values.yaml#L1271) | list | Additional mounts | `[]` | +| [persistence.size](./values.yaml#L1255) | string | The size of the PVC | `"8Gi"` | +| [persistence.storageClass](./values.yaml#L1247) | string | Storage class for the PVC | `nil` | +| [persistence.subPath](./values.yaml#L1264) | string | SubPath for jenkins-home mount | `nil` | +| [persistence.volumes](./values.yaml#L1266) | list | Additional volumes | `[]` | +| [rbac.create](./values.yaml#L1303) | bool | Whether RBAC resources are created | `true` | +| [rbac.readSecrets](./values.yaml#L1305) | bool | Whether the Jenkins service account should be able to read Kubernetes secrets | `false` | +| [renderHelmLabels](./values.yaml#L30) | bool | Enables rendering of the helm.sh/chart label to the annotations | `true` | +| [serviceAccount.annotations](./values.yaml#L1315) | object | Configures annotations for the ServiceAccount | `{}` | +| [serviceAccount.create](./values.yaml#L1309) | bool | Configures if a ServiceAccount with this name should be created | `true` | +| [serviceAccount.extraLabels](./values.yaml#L1317) | object | Configures extra labels for the ServiceAccount | `{}` | +| [serviceAccount.imagePullSecretName](./values.yaml#L1319) | string | Controller ServiceAccount image pull secret | `nil` | +| [serviceAccount.name](./values.yaml#L1313) | string | | `nil` | +| [serviceAccountAgent.annotations](./values.yaml#L1330) | object | Configures annotations for the agent ServiceAccount | `{}` | +| [serviceAccountAgent.create](./values.yaml#L1324) | bool | Configures if an agent ServiceAccount should be created | `false` | +| [serviceAccountAgent.extraLabels](./values.yaml#L1332) | object | Configures extra labels for the agent ServiceAccount | `{}` | +| [serviceAccountAgent.imagePullSecretName](./values.yaml#L1334) | string | Agent ServiceAccount image pull secret | `nil` | +| [serviceAccountAgent.name](./values.yaml#L1328) | string | The name of the agent ServiceAccount to be used by access-controlled resources | `nil` | diff --git a/charts/jenkins/jenkins/5.5.12/VALUES.md.gotmpl b/charts/jenkins/jenkins/5.5.12/VALUES.md.gotmpl new file mode 100644 index 0000000000..21080e35ae --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/VALUES.md.gotmpl @@ -0,0 +1,28 @@ +# Jenkins + +## Configuration + +The following tables list the configurable parameters of the Jenkins chart and their default values. + +{{- define "chart.valueDefaultColumnRender" -}} +{{- $defaultValue := (trimAll "`" (default .Default .AutoDefault) | replace "\n" "") -}} +`{{- $defaultValue | replace "\n" "" -}}` +{{- end -}} + +{{- define "chart.typeColumnRender" -}} +{{- .Type -}} +{{- end -}} + +{{- define "chart.valueDescription" -}} +{{- default .Description .AutoDescription }} +{{- end -}} + +{{- define "chart.valuesTable" -}} +| Key | Type | Description | Default | +|:----|:-----|:---------|:------------| +{{- range .Values }} +| [{{ .Key }}](./values.yaml#L{{ .LineNumber }}) | {{ template "chart.typeColumnRender" . }} | {{ template "chart.valueDescription" . }} | {{ template "chart.valueDefaultColumnRender" . }} | +{{- end }} +{{- end }} + +{{ template "chart.valuesSection" . }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/NOTES.txt b/charts/jenkins/jenkins/5.5.12/templates/NOTES.txt new file mode 100644 index 0000000000..953dd26067 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/NOTES.txt @@ -0,0 +1,68 @@ +{{- $prefix := .Values.controller.jenkinsUriPrefix | default "" -}} +{{- $url := "" -}} +1. Get your '{{ .Values.controller.admin.username }}' user password by running: + kubectl exec --namespace {{ template "jenkins.namespace" . }} -it svc/{{ template "jenkins.fullname" . }} -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password && echo +{{- if .Values.controller.ingress.hostName -}} +{{- if .Values.controller.ingress.tls -}} +{{- $url = print "https://" .Values.controller.ingress.hostName $prefix -}} +{{- else -}} +{{- $url = print "http://" .Values.controller.ingress.hostName $prefix -}} +{{- end }} +2. Visit {{ $url }} +{{- else }} +2. Get the Jenkins URL to visit by running these commands in the same shell: +{{- if contains "NodePort" .Values.controller.serviceType }} + export NODE_PORT=$(kubectl get --namespace {{ template "jenkins.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "jenkins.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ template "jenkins.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://$NODE_IP:$NODE_PORT" $prefix -}} +{{- else -}} +{{- $url = print "http://$NODE_IP:$NODE_PORT" $prefix -}} +{{- end }} + echo {{ $url }} + +{{- else if contains "LoadBalancer" .Values.controller.serviceType }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc --namespace {{ template "jenkins.namespace" . }} -w {{ template "jenkins.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ template "jenkins.namespace" . }} {{ template "jenkins.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://$SERVICE_IP:" .Values.controller.servicePort $prefix -}} +{{- else -}} +{{- $url = print "http://$SERVICE_IP:" .Values.controller.servicePort $prefix -}} +{{- end }} + echo {{ $url }} + +{{- else if contains "ClusterIP" .Values.controller.serviceType -}} +{{- if .Values.controller.httpsKeyStore.enable -}} +{{- $url = print "https://127.0.0.1:" .Values.controller.servicePort $prefix -}} +{{- else -}} +{{- $url = print "http://127.0.0.1:" .Values.controller.servicePort $prefix -}} +{{- end }} + echo {{ $url }} + kubectl --namespace {{ template "jenkins.namespace" . }} port-forward svc/{{template "jenkins.fullname" . }} {{ .Values.controller.servicePort }}:{{ .Values.controller.servicePort }} +{{- end }} +{{- end }} + +3. Login with the password from step 1 and the username: {{ .Values.controller.admin.username }} +4. Configure security realm and authorization strategy +5. Use Jenkins Configuration as Code by specifying configScripts in your values.yaml file, see documentation: {{ $url }}/configuration-as-code and examples: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos + +For more information on running Jenkins on Kubernetes, visit: +https://cloud.google.com/solutions/jenkins-on-container-engine + +For more information about Jenkins Configuration as Code, visit: +https://jenkins.io/projects/jcasc/ + +{{ if and (eq .Values.controller.image.repository "jenkins/jenkins") (eq .Values.controller.image.registry "docker.io") }} +NOTE: Consider using a custom image with pre-installed plugins +{{- else if .Values.controller.installPlugins }} +NOTE: Consider disabling `installPlugins` if your image already contains plugins. +{{- end }} + +{{- if .Values.persistence.enabled }} +{{- else }} +################################################################################# +###### WARNING: Persistence is disabled!!! You will lose your data when ##### +###### the Jenkins pod is terminated. ##### +################################################################################# +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/_helpers.tpl b/charts/jenkins/jenkins/5.5.12/templates/_helpers.tpl new file mode 100644 index 0000000000..dd3895b65c --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/_helpers.tpl @@ -0,0 +1,684 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "jenkins.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Expand the label of the chart. +*/}} +{{- define "jenkins.label" -}} +{{- printf "%s-%s" (include "jenkins.name" .) .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "jenkins.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{- define "jenkins.agent.namespace" -}} + {{- if .Values.agent.namespace -}} + {{- tpl .Values.agent.namespace . -}} + {{- else -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} + {{- end -}} +{{- 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 "jenkins.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{/* +Returns the admin password +https://github.com/helm/charts/issues/5167#issuecomment-619137759 +*/}} +{{- define "jenkins.password" -}} + {{- if .Values.controller.admin.password -}} + {{- .Values.controller.admin.password | b64enc | quote }} + {{- else -}} + {{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "jenkins.fullname" .)).data -}} + {{- if $secret -}} + {{/* + Reusing current password since secret exists + */}} + {{- index $secret ( .Values.controller.admin.passwordKey | default "jenkins-admin-password" ) -}} + {{- else -}} + {{/* + Generate new password + */}} + {{- randAlphaNum 22 | b64enc | quote }} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Returns the Jenkins URL +*/}} +{{- define "jenkins.url" -}} +{{- if .Values.controller.jenkinsUrl }} + {{- .Values.controller.jenkinsUrl }} +{{- else }} + {{- if .Values.controller.ingress.hostName }} + {{- if .Values.controller.ingress.tls }} + {{- default "https" .Values.controller.jenkinsUrlProtocol }}://{{ tpl .Values.controller.ingress.hostName $ }}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- else }} + {{- default "http" .Values.controller.jenkinsUrlProtocol }}://{{ tpl .Values.controller.ingress.hostName $ }}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- end }} + {{- else }} + {{- default "http" .Values.controller.jenkinsUrlProtocol }}://{{ template "jenkins.fullname" . }}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }} + {{- end}} +{{- end}} +{{- end -}} + +{{/* +Returns configuration as code default config +*/}} +{{- define "jenkins.casc.defaults" -}} +jenkins: + {{- $configScripts := toYaml .Values.controller.JCasC.configScripts }} + {{- if and (.Values.controller.JCasC.authorizationStrategy) (not (contains "authorizationStrategy:" $configScripts)) }} + authorizationStrategy: + {{- tpl .Values.controller.JCasC.authorizationStrategy . | nindent 4 }} + {{- end }} + {{- if and (.Values.controller.JCasC.securityRealm) (not (contains "securityRealm:" $configScripts)) }} + securityRealm: + {{- tpl .Values.controller.JCasC.securityRealm . | nindent 4 }} + {{- end }} + disableRememberMe: {{ .Values.controller.disableRememberMe }} + {{- if .Values.controller.legacyRemotingSecurityEnabled }} + remotingSecurity: + enabled: true + {{- end }} + mode: {{ .Values.controller.executorMode }} + numExecutors: {{ .Values.controller.numExecutors }} + {{- if not (kindIs "invalid" .Values.controller.customJenkinsLabels) }} + labelString: "{{ join " " .Values.controller.customJenkinsLabels }}" + {{- end }} + {{- if .Values.controller.projectNamingStrategy }} + {{- if kindIs "string" .Values.controller.projectNamingStrategy }} + projectNamingStrategy: "{{ .Values.controller.projectNamingStrategy }}" + {{- else }} + projectNamingStrategy: + {{- toYaml .Values.controller.projectNamingStrategy | nindent 4 }} + {{- end }} + {{- end }} + markupFormatter: + {{- if .Values.controller.enableRawHtmlMarkupFormatter }} + rawHtml: + disableSyntaxHighlighting: true + {{- else }} + {{- toYaml .Values.controller.markupFormatter | nindent 4 }} + {{- end }} + clouds: + - kubernetes: + containerCapStr: "{{ .Values.agent.containerCap }}" + {{- if .Values.agent.garbageCollection.enabled }} + garbageCollection: + {{- if .Values.agent.garbageCollection.namespaces }} + namespaces: |- + {{- .Values.agent.garbageCollection.namespaces | nindent 10 }} + {{- end }} + timeout: "{{ .Values.agent.garbageCollection.timeout }}" + {{- end }} + {{- if .Values.agent.jnlpregistry }} + jnlpregistry: "{{ .Values.agent.jnlpregistry }}" + {{- end }} + defaultsProviderTemplate: "{{ .Values.agent.defaultsProviderTemplate }}" + connectTimeout: "{{ .Values.agent.kubernetesConnectTimeout }}" + readTimeout: "{{ .Values.agent.kubernetesReadTimeout }}" + {{- if .Values.agent.directConnection }} + directConnection: true + {{- else }} + {{- if .Values.agent.jenkinsUrl }} + jenkinsUrl: "{{ tpl .Values.agent.jenkinsUrl . }}" + {{- else }} + jenkinsUrl: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- if not .Values.agent.websocket }} + {{- if .Values.agent.jenkinsTunnel }} + jenkinsTunnel: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + jenkinsTunnel: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + webSocket: true + {{- end }} + {{- end }} + skipTlsVerify: {{ .Values.agent.skipTlsVerify | default false}} + usageRestricted: {{ .Values.agent.usageRestricted | default false}} + maxRequestsPerHostStr: {{ .Values.agent.maxRequestsPerHostStr | quote }} + retentionTimeout: {{ .Values.agent.retentionTimeout | quote }} + waitForPodSec: {{ .Values.agent.waitForPodSec | quote }} + name: "{{ .Values.controller.cloudName }}" + namespace: "{{ template "jenkins.agent.namespace" . }}" + restrictedPssSecurityContext: {{ .Values.agent.restrictedPssSecurityContext }} + serverUrl: "{{ .Values.kubernetesURL }}" + credentialsId: "{{ .Values.credentialsId }}" + {{- if .Values.agent.enabled }} + podLabels: + - key: "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}" + value: "true" + {{- range $key, $val := .Values.agent.podLabels }} + - key: {{ $key | quote }} + value: {{ $val | quote }} + {{- end }} + templates: + {{- if not .Values.agent.disableDefaultAgent }} + {{- include "jenkins.casc.podTemplate" . | nindent 8 }} + {{- end }} + {{- if .Values.additionalAgents }} + {{- /* save .Values.agent */}} + {{- $agent := .Values.agent }} + {{- range $name, $additionalAgent := .Values.additionalAgents }} + {{- $additionalContainersEmpty := and (hasKey $additionalAgent "additionalContainers") (empty $additionalAgent.additionalContainers) }} + {{- /* merge original .Values.agent into additional agent to ensure it at least has the default values */}} + {{- $additionalAgent := merge $additionalAgent $agent }} + {{- /* clear list of additional containers in case it is configured empty for this agent (merge might have overwritten that) */}} + {{- if $additionalContainersEmpty }} + {{- $_ := set $additionalAgent "additionalContainers" list }} + {{- end }} + {{- /* set .Values.agent to $additionalAgent */}} + {{- $_ := set $.Values "agent" $additionalAgent }} + {{- include "jenkins.casc.podTemplate" $ | nindent 8 }} + {{- end }} + {{- /* restore .Values.agent */}} + {{- $_ := set .Values "agent" $agent }} + {{- end }} + {{- if .Values.agent.podTemplates }} + {{- range $key, $val := .Values.agent.podTemplates }} + {{- tpl $val $ | nindent 8 }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.additionalClouds }} + {{- /* save root */}} + {{- $oldRoot := deepCopy $ }} + {{- range $name, $additionalCloud := .Values.additionalClouds }} + {{- $newRoot := deepCopy $ }} + {{- /* clear additionalAgents from the copy if override set to `true` */}} + {{- if .additionalAgentsOverride }} + {{- $_ := set $newRoot.Values "additionalAgents" list}} + {{- end}} + {{- $newValues := merge $additionalCloud $newRoot.Values }} + {{- $_ := set $newRoot "Values" $newValues }} + {{- /* clear additionalClouds from the copy */}} + {{- $_ := set $newRoot.Values "additionalClouds" list }} + {{- with $newRoot}} + - kubernetes: + containerCapStr: "{{ .Values.agent.containerCap }}" + {{- if .Values.agent.jnlpregistry }} + jnlpregistry: "{{ .Values.agent.jnlpregistry }}" + {{- end }} + defaultsProviderTemplate: "{{ .Values.agent.defaultsProviderTemplate }}" + connectTimeout: "{{ .Values.agent.kubernetesConnectTimeout }}" + readTimeout: "{{ .Values.agent.kubernetesReadTimeout }}" + {{- if .Values.agent.directConnection }} + directConnection: true + {{- else }} + {{- if .Values.agent.jenkinsUrl }} + jenkinsUrl: "{{ tpl .Values.agent.jenkinsUrl . }}" + {{- else }} + jenkinsUrl: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- if not .Values.agent.websocket }} + {{- if .Values.agent.jenkinsTunnel }} + jenkinsTunnel: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + jenkinsTunnel: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + webSocket: true + {{- end }} + {{- end }} + skipTlsVerify: {{ .Values.agent.skipTlsVerify | default false}} + usageRestricted: {{ .Values.agent.usageRestricted | default false}} + maxRequestsPerHostStr: {{ .Values.agent.maxRequestsPerHostStr | quote }} + retentionTimeout: {{ .Values.agent.retentionTimeout | quote }} + waitForPodSec: {{ .Values.agent.waitForPodSec | quote }} + name: {{ $name | quote }} + namespace: "{{ template "jenkins.agent.namespace" . }}" + restrictedPssSecurityContext: {{ .Values.agent.restrictedPssSecurityContext }} + serverUrl: "{{ .Values.kubernetesURL }}" + credentialsId: "{{ .Values.credentialsId }}" + {{- if .Values.agent.enabled }} + podLabels: + - key: "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}" + value: "true" + {{- range $key, $val := .Values.agent.podLabels }} + - key: {{ $key | quote }} + value: {{ $val | quote }} + {{- end }} + templates: + {{- if not .Values.agent.disableDefaultAgent }} + {{- include "jenkins.casc.podTemplate" . | nindent 8 }} + {{- end }} + {{- if .Values.additionalAgents }} + {{- /* save .Values.agent */}} + {{- $agent := .Values.agent }} + {{- range $name, $additionalAgent := .Values.additionalAgents }} + {{- $additionalContainersEmpty := and (hasKey $additionalAgent "additionalContainers") (empty $additionalAgent.additionalContainers) }} + {{- /* merge original .Values.agent into additional agent to ensure it at least has the default values */}} + {{- $additionalAgent := merge $additionalAgent $agent }} + {{- /* clear list of additional containers in case it is configured empty for this agent (merge might have overwritten that) */}} + {{- if $additionalContainersEmpty }} + {{- $_ := set $additionalAgent "additionalContainers" list }} + {{- end }} + {{- /* set .Values.agent to $additionalAgent */}} + {{- $_ := set $.Values "agent" $additionalAgent }} + {{- include "jenkins.casc.podTemplate" $ | nindent 8 }} + {{- end }} + {{- /* restore .Values.agent */}} + {{- $_ := set .Values "agent" $agent }} + {{- end }} + {{- with .Values.agent.podTemplates }} + {{- range $key, $val := . }} + {{- tpl $val $ | nindent 8 }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- /* restore root */}} + {{- $_ := set $ "Values" $oldRoot.Values }} + {{- end }} + {{- if .Values.controller.csrf.defaultCrumbIssuer.enabled }} + crumbIssuer: + standard: + excludeClientIPFromCrumb: {{ if .Values.controller.csrf.defaultCrumbIssuer.proxyCompatability }}true{{ else }}false{{- end }} + {{- end }} +{{- include "jenkins.casc.security" . }} +{{- with .Values.controller.scriptApproval }} + scriptApproval: + approvedSignatures: + {{- range $key, $val := . }} + - "{{ $val }}" + {{- end }} +{{- end }} +unclassified: + location: + {{- with .Values.controller.jenkinsAdminEmail }} + adminAddress: {{ . }} + {{- end }} + url: {{ template "jenkins.url" . }} +{{- end -}} + +{{/* +Returns a name template to be used for jcasc configmaps, using +suffix passed in at call as index 0 +*/}} +{{- define "jenkins.casc.configName" -}} +{{- $name := index . 0 -}} +{{- $root := index . 1 -}} +"{{- include "jenkins.fullname" $root -}}-jenkins-{{ $name }}" +{{- end -}} + +{{/* +Returns kubernetes pod template configuration as code +*/}} +{{- define "jenkins.casc.podTemplate" -}} +- name: "{{ .Values.agent.podName }}" + namespace: "{{ template "jenkins.agent.namespace" . }}" +{{- if .Values.agent.annotations }} + annotations: + {{- range $key, $value := .Values.agent.annotations }} + - key: {{ $key }} + value: {{ $value | quote }} + {{- end }} +{{- end }} + id: {{ sha256sum (toYaml .Values.agent) }} + containers: + - name: "{{ .Values.agent.sideContainerName }}" + alwaysPullImage: {{ .Values.agent.alwaysPullImage }} + args: "{{ .Values.agent.args | replace "$" "^$" }}" + {{- with .Values.agent.command }} + command: {{ . }} + {{- end }} + envVars: + - envVar: + {{- if .Values.agent.directConnection }} + key: "JENKINS_DIRECT_CONNECTION" + {{- if .Values.agent.jenkinsTunnel }} + value: "{{ tpl .Values.agent.jenkinsTunnel . }}" + {{- else }} + value: "{{ template "jenkins.fullname" . }}-agent.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{ .Values.controller.agentListenerPort }}" + {{- end }} + {{- else }} + key: "JENKINS_URL" + {{- if .Values.agent.jenkinsUrl }} + value: {{ tpl .Values.agent.jenkinsUrl . }} + {{- else }} + value: "http://{{ template "jenkins.fullname" . }}.{{ template "jenkins.namespace" . }}.svc.{{.Values.clusterZone}}:{{.Values.controller.servicePort}}{{ default "/" .Values.controller.jenkinsUriPrefix }}" + {{- end }} + {{- end }} + image: "{{ .Values.agent.image.repository }}:{{ .Values.agent.image.tag }}" + {{- if .Values.agent.livenessProbe }} + livenessProbe: + execArgs: {{.Values.agent.livenessProbe.execArgs | quote}} + failureThreshold: {{.Values.agent.livenessProbe.failureThreshold}} + initialDelaySeconds: {{.Values.agent.livenessProbe.initialDelaySeconds}} + periodSeconds: {{.Values.agent.livenessProbe.periodSeconds}} + successThreshold: {{.Values.agent.livenessProbe.successThreshold}} + timeoutSeconds: {{.Values.agent.livenessProbe.timeoutSeconds}} + {{- end }} + privileged: "{{- if .Values.agent.privileged }}true{{- else }}false{{- end }}" + resourceLimitCpu: {{.Values.agent.resources.limits.cpu}} + resourceLimitMemory: {{.Values.agent.resources.limits.memory}} + {{- with .Values.agent.resources.limits.ephemeralStorage }} + resourceLimitEphemeralStorage: {{.}} + {{- end }} + resourceRequestCpu: {{.Values.agent.resources.requests.cpu}} + resourceRequestMemory: {{.Values.agent.resources.requests.memory}} + {{- with .Values.agent.resources.requests.ephemeralStorage }} + resourceRequestEphemeralStorage: {{.}} + {{- end }} + {{- with .Values.agent.runAsUser }} + runAsUser: {{ . }} + {{- end }} + {{- with .Values.agent.runAsGroup }} + runAsGroup: {{ . }} + {{- end }} + ttyEnabled: {{ .Values.agent.TTYEnabled }} + workingDir: {{ .Values.agent.workingDir }} +{{- range $additionalContainers := .Values.agent.additionalContainers }} + - name: "{{ $additionalContainers.sideContainerName }}" + alwaysPullImage: {{ $additionalContainers.alwaysPullImage | default $.Values.agent.alwaysPullImage }} + args: "{{ $additionalContainers.args | replace "$" "^$" }}" + {{- with $additionalContainers.command }} + command: {{ . }} + {{- end }} + envVars: + - envVar: + key: "JENKINS_URL" + {{- if $additionalContainers.jenkinsUrl }} + value: {{ tpl ($additionalContainers.jenkinsUrl) . }} + {{- else }} + value: "http://{{ template "jenkins.fullname" $ }}.{{ template "jenkins.namespace" $ }}.svc.{{ $.Values.clusterZone }}:{{ $.Values.controller.servicePort }}{{ default "/" $.Values.controller.jenkinsUriPrefix }}" + {{- end }} + image: "{{ $additionalContainers.image.repository }}:{{ $additionalContainers.image.tag }}" + {{- if $additionalContainers.livenessProbe }} + livenessProbe: + execArgs: {{$additionalContainers.livenessProbe.execArgs | quote}} + failureThreshold: {{$additionalContainers.livenessProbe.failureThreshold}} + initialDelaySeconds: {{$additionalContainers.livenessProbe.initialDelaySeconds}} + periodSeconds: {{$additionalContainers.livenessProbe.periodSeconds}} + successThreshold: {{$additionalContainers.livenessProbe.successThreshold}} + timeoutSeconds: {{$additionalContainers.livenessProbe.timeoutSeconds}} + {{- end }} + privileged: "{{- if $additionalContainers.privileged }}true{{- else }}false{{- end }}" + resourceLimitCpu: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.limits.cpu }}{{ else }}{{ $.Values.agent.resources.limits.cpu }}{{ end }} + resourceLimitMemory: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.limits.memory }}{{ else }}{{ $.Values.agent.resources.limits.memory }}{{ end }} + resourceRequestCpu: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.requests.cpu }}{{ else }}{{ $.Values.agent.resources.requests.cpu }}{{ end }} + resourceRequestMemory: {{ if $additionalContainers.resources }}{{ $additionalContainers.resources.requests.memory }}{{ else }}{{ $.Values.agent.resources.requests.memory }}{{ end }} + {{- if or $additionalContainers.runAsUser $.Values.agent.runAsUser }} + runAsUser: {{ $additionalContainers.runAsUser | default $.Values.agent.runAsUser }} + {{- end }} + {{- if or $additionalContainers.runAsGroup $.Values.agent.runAsGroup }} + runAsGroup: {{ $additionalContainers.runAsGroup | default $.Values.agent.runAsGroup }} + {{- end }} + ttyEnabled: {{ $additionalContainers.TTYEnabled | default $.Values.agent.TTYEnabled }} + workingDir: {{ $additionalContainers.workingDir | default $.Values.agent.workingDir }} +{{- end }} +{{- if or .Values.agent.envVars .Values.agent.secretEnvVars }} + envVars: + {{- range $index, $var := .Values.agent.envVars }} + - envVar: + key: {{ $var.name }} + value: {{ tpl $var.value $ }} + {{- end }} + {{- range $index, $var := .Values.agent.secretEnvVars }} + - secretEnvVar: + key: {{ $var.key }} + secretName: {{ $var.secretName }} + secretKey: {{ $var.secretKey }} + optional: {{ $var.optional | default false }} + {{- end }} +{{- end }} + idleMinutes: {{ .Values.agent.idleMinutes }} + instanceCap: 2147483647 + {{- if .Values.agent.hostNetworking }} + hostNetwork: {{ .Values.agent.hostNetworking }} + {{- end }} + {{- if .Values.agent.imagePullSecretName }} + imagePullSecrets: + - name: {{ .Values.agent.imagePullSecretName }} + {{- end }} + label: "{{ .Release.Name }}-{{ .Values.agent.componentName }} {{ .Values.agent.customJenkinsLabels | join " " }}" +{{- if .Values.agent.nodeSelector }} + nodeSelector: + {{- $local := dict "first" true }} + {{- range $key, $value := .Values.agent.nodeSelector }} + {{- if $local.first }} {{ else }},{{ end }} + {{- $key }}={{ tpl $value $ }} + {{- $_ := set $local "first" false }} + {{- end }} +{{- end }} + nodeUsageMode: {{ quote .Values.agent.nodeUsageMode }} + podRetention: {{ .Values.agent.podRetention }} + showRawYaml: {{ .Values.agent.showRawYaml }} +{{- $asaname := default (include "jenkins.serviceAccountAgentName" .) .Values.agent.serviceAccount -}} +{{- if or (.Values.agent.useDefaultServiceAccount) (.Values.agent.serviceAccount) }} + serviceAccount: "{{ $asaname }}" +{{- end }} + slaveConnectTimeoutStr: "{{ .Values.agent.connectTimeout }}" +{{- if .Values.agent.volumes }} + volumes: + {{- range $index, $volume := .Values.agent.volumes }} + -{{- if (eq $volume.type "ConfigMap") }} configMapVolume: + {{- else if (eq $volume.type "EmptyDir") }} emptyDirVolume: + {{- else if (eq $volume.type "EphemeralVolume") }} genericEphemeralVolume: + {{- else if (eq $volume.type "HostPath") }} hostPathVolume: + {{- else if (eq $volume.type "Nfs") }} nfsVolume: + {{- else if (eq $volume.type "PVC") }} persistentVolumeClaim: + {{- else if (eq $volume.type "Secret") }} secretVolume: + {{- else }} {{ $volume.type }}: + {{- end }} + {{- range $key, $value := $volume }} + {{- if not (eq $key "type") }} + {{ $key }}: {{ if kindIs "string" $value }}{{ tpl $value $ | quote }}{{ else }}{{ $value }}{{ end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- if .Values.agent.workspaceVolume }} + workspaceVolume: + {{- if (eq .Values.agent.workspaceVolume.type "DynamicPVC") }} + dynamicPVC: + {{- else if (eq .Values.agent.workspaceVolume.type "EmptyDir") }} + emptyDirWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "EphemeralVolume") }} + genericEphemeralVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "HostPath") }} + hostPathWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "Nfs") }} + nfsWorkspaceVolume: + {{- else if (eq .Values.agent.workspaceVolume.type "PVC") }} + persistentVolumeClaimWorkspaceVolume: + {{- else }} + {{ .Values.agent.workspaceVolume.type }}: + {{- end }} + {{- range $key, $value := .Values.agent.workspaceVolume }} + {{- if not (eq $key "type") }} + {{ $key }}: {{ if kindIs "string" $value }}{{ tpl $value $ | quote }}{{ else }}{{ $value }}{{ end }} + {{- end }} + {{- end }} +{{- end }} +{{- if .Values.agent.yamlTemplate }} + yaml: |- + {{- tpl (trim .Values.agent.yamlTemplate) . | nindent 4 }} +{{- end }} + yamlMergeStrategy: {{ .Values.agent.yamlMergeStrategy }} + inheritYamlMergeStrategy: {{ .Values.agent.inheritYamlMergeStrategy }} +{{- end -}} + +{{- define "jenkins.kubernetes-version" -}} + {{- if .Values.controller.installPlugins -}} + {{- range .Values.controller.installPlugins -}} + {{- if hasPrefix "kubernetes:" . }} + {{- $split := splitList ":" . }} + {{- printf "%s" (index $split 1 ) -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- define "jenkins.casc.security" }} +security: +{{- with .Values.controller.JCasC }} +{{- if .security }} + {{- .security | toYaml | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "jenkins.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "jenkins.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account for Jenkins agents to use +*/}} +{{- define "jenkins.serviceAccountAgentName" -}} +{{- if .Values.serviceAccountAgent.create -}} + {{ default (printf "%s-%s" (include "jenkins.fullname" .) "agent") .Values.serviceAccountAgent.name }} +{{- else -}} + {{ default "default" .Values.serviceAccountAgent.name }} +{{- end -}} +{{- end -}} + +{{/* +Create a full tag name for controller image +*/}} +{{- define "controller.image.tag" -}} +{{- if .Values.controller.image.tagLabel -}} + {{- default (printf "%s-%s" .Chart.AppVersion .Values.controller.image.tagLabel) .Values.controller.image.tag -}} +{{- else -}} + {{- default .Chart.AppVersion .Values.controller.image.tag -}} +{{- end -}} +{{- end -}} + +{{/* +Create the HTTP port for interacting with the controller +*/}} +{{- define "controller.httpPort" -}} +{{- if .Values.controller.httpsKeyStore.enable -}} + {{- .Values.controller.httpsKeyStore.httpPort -}} +{{- else -}} + {{- .Values.controller.targetPort -}} +{{- end -}} +{{- end -}} + +{{- define "jenkins.configReloadContainer" -}} +{{- $root := index . 0 -}} +{{- $containerName := index . 1 -}} +{{- $containerType := index . 2 -}} +- name: {{ $containerName }} + image: "{{ $root.Values.controller.sidecars.configAutoReload.image.registry }}/{{ $root.Values.controller.sidecars.configAutoReload.image.repository }}:{{ $root.Values.controller.sidecars.configAutoReload.image.tag }}" + imagePullPolicy: {{ $root.Values.controller.sidecars.configAutoReload.imagePullPolicy }} + {{- if $root.Values.controller.sidecars.configAutoReload.containerSecurityContext }} + securityContext: {{- toYaml $root.Values.controller.sidecars.configAutoReload.containerSecurityContext | nindent 4 }} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.envFrom }} + envFrom: +{{ (tpl (toYaml $root.Values.controller.sidecars.configAutoReload.envFrom) $root) | indent 4 }} + {{- end }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: LABEL + value: "{{ template "jenkins.fullname" $root }}-jenkins-config" + - name: FOLDER + value: "{{ $root.Values.controller.sidecars.configAutoReload.folder }}" + - name: NAMESPACE + value: '{{ $root.Values.controller.sidecars.configAutoReload.searchNamespace | default (include "jenkins.namespace" $root) }}' + {{- if eq $containerType "init" }} + - name: METHOD + value: "LIST" + {{- else if $root.Values.controller.sidecars.configAutoReload.sleepTime }} + - name: METHOD + value: "SLEEP" + - name: SLEEP_TIME + value: "{{ $root.Values.controller.sidecars.configAutoReload.sleepTime }}" + {{- end }} + {{- if eq $containerType "sidecar" }} + - name: REQ_URL + value: "{{- default "http" $root.Values.controller.sidecars.configAutoReload.scheme }}://localhost:{{- include "controller.httpPort" $root -}}{{- $root.Values.controller.jenkinsUriPrefix -}}/reload-configuration-as-code/?casc-reload-token=$(POD_NAME)" + - name: REQ_METHOD + value: "POST" + - name: REQ_RETRY_CONNECT + value: "{{ $root.Values.controller.sidecars.configAutoReload.reqRetryConnect }}" + {{- if $root.Values.controller.sidecars.configAutoReload.skipTlsVerify }} + - name: REQ_SKIP_TLS_VERIFY + value: "true" + {{- end }} + {{- end }} + + {{- if $root.Values.controller.sidecars.configAutoReload.env }} + {{- range $envVarItem := $root.Values.controller.sidecars.configAutoReload.env -}} + {{- if or (ne $containerType "init") (ne .name "METHOD") }} +{{- (tpl (toYaml (list $envVarItem)) $root) | nindent 4 }} + {{- end -}} + {{- end -}} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: LOG_CONFIG + value: "{{ $root.Values.controller.jenkinsHome }}/auto-reload/auto-reload-config.yaml" + {{- end }} + + resources: +{{ toYaml $root.Values.controller.sidecars.configAutoReload.resources | indent 4 }} + volumeMounts: + - name: sc-config-volume + mountPath: {{ $root.Values.controller.sidecars.configAutoReload.folder | quote }} + - name: jenkins-home + mountPath: {{ $root.Values.controller.jenkinsHome }} + {{- if $root.Values.persistence.subPath }} + subPath: {{ $root.Values.persistence.subPath }} + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: auto-reload-config + mountPath: {{ $root.Values.controller.jenkinsHome }}/auto-reload + - name: auto-reload-config-logs + mountPath: {{ $root.Values.controller.jenkinsHome }}/auto-reload-logs + {{- end }} + {{- if $root.Values.controller.sidecars.configAutoReload.additionalVolumeMounts }} +{{ (tpl (toYaml $root.Values.controller.sidecars.configAutoReload.additionalVolumeMounts) $root) | indent 4 }} + {{- end }} + +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.12/templates/auto-reload-config.yaml b/charts/jenkins/jenkins/5.5.12/templates/auto-reload-config.yaml new file mode 100644 index 0000000000..8c177d7f3d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/auto-reload-config.yaml @@ -0,0 +1,60 @@ +{{- if .Values.controller.sidecars.configAutoReload.logging.configuration.override }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-auto-reload-config + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" . }} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" +data: + auto-reload-config.yaml: |- + version: 1 + disable_existing_loggers: false + root: + level: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.logLevel }} + handlers: + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToConsole}} + - console + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToFile }} + - file + {{- end }} + handlers: + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToConsole}} + console: + class: logging.StreamHandler + level: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.logLevel }} + formatter: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.formatter }} + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.logToFile }} + file: + class : logging.handlers.RotatingFileHandler + formatter: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.formatter }} + filename: {{ .Values.controller.jenkinsHome }}/auto-reload-logs/file.log + maxBytes: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.maxBytes }} + backupCount: {{ .Values.controller.sidecars.configAutoReload.logging.configuration.backupCount }} + {{- end }} + formatters: + JSON: + "()": logger.JsonFormatter + format: "%(levelname)s %(message)s" + rename_fields: + message: msg + levelname: level + LOGFMT: + "()": logger.LogfmtFormatter + keys: + - time + - level + - msg + mapping: + time: asctime + level: levelname + msg: message + {{- end }} \ No newline at end of file diff --git a/charts/jenkins/jenkins/5.5.12/templates/config-init-scripts.yaml b/charts/jenkins/jenkins/5.5.12/templates/config-init-scripts.yaml new file mode 100644 index 0000000000..7dd253cc35 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/config-init-scripts.yaml @@ -0,0 +1,18 @@ +{{- if .Values.controller.initScripts -}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-init-scripts + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +data: +{{- range $key, $val := .Values.controller.initScripts }} + init{{ $key }}.groovy: |- +{{ tpl $val $ | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/config.yaml b/charts/jenkins/jenkins/5.5.12/templates/config.yaml new file mode 100644 index 0000000000..5de0b9f728 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/config.yaml @@ -0,0 +1,92 @@ +{{- $jenkinsHome := .Values.controller.jenkinsHome -}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +data: + apply_config.sh: |- + set -e +{{- if .Values.controller.initializeOnce }} + if [ -f {{ .Values.controller.jenkinsHome }}/initialization-completed ]; then + echo "controller was previously initialized, refusing to re-initialize" + exit 0 + fi +{{- end }} + echo "disable Setup Wizard" + # Prevent Setup Wizard when JCasC is enabled + echo $JENKINS_VERSION > {{ .Values.controller.jenkinsHome }}/jenkins.install.UpgradeWizard.state + echo $JENKINS_VERSION > {{ .Values.controller.jenkinsHome }}/jenkins.install.InstallUtil.lastExecVersion +{{- if .Values.controller.overwritePlugins }} + echo "remove all plugins from shared volume" + # remove all plugins from shared volume + rm -rf {{ .Values.controller.jenkinsHome }}/plugins/* +{{- end }} +{{- if .Values.controller.JCasC.overwriteConfiguration }} + echo "deleting all XML config files" + rm -f {{ .Values.controller.jenkinsHome }}/config.xml + rm -f {{ .Values.controller.jenkinsHome }}/*plugins*.xml + find {{ .Values.controller.jenkinsHome }} -maxdepth 1 -type f -iname '*configuration*.xml' -exec rm -f {} \; +{{- end }} +{{- if .Values.controller.installPlugins }} + echo "download plugins" + # Install missing plugins + cp /var/jenkins_config/plugins.txt {{ .Values.controller.jenkinsHome }}; + rm -rf {{ .Values.controller.jenkinsRef }}/plugins/*.lock + version () { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } + if [ -f "{{ .Values.controller.jenkinsWar }}" ] && [ -n "$(command -v jenkins-plugin-cli)" 2>/dev/null ] && [ $(version $(jenkins-plugin-cli --version)) -ge $(version "2.1.1") ]; then + jenkins-plugin-cli --verbose --war "{{ .Values.controller.jenkinsWar }}" --plugin-file "{{ .Values.controller.jenkinsHome }}/plugins.txt" --latest {{ .Values.controller.installLatestPlugins }}{{- if .Values.controller.installLatestSpecifiedPlugins }} --latest-specified{{- end }}; + else + /usr/local/bin/install-plugins.sh `echo $(cat {{ .Values.controller.jenkinsHome }}/plugins.txt)`; + fi + echo "copy plugins to shared volume" + # Copy plugins to shared volume + yes n | cp -i {{ .Values.controller.jenkinsRef }}/plugins/* /var/jenkins_plugins/; +{{- end }} + {{- if not .Values.controller.sidecars.configAutoReload.enabled }} + echo "copy configuration as code files" + mkdir -p {{ .Values.controller.jenkinsHome }}/casc_configs; + rm -rf {{ .Values.controller.jenkinsHome }}/casc_configs/* + {{- if or .Values.controller.JCasC.defaultConfig .Values.controller.JCasC.configScripts }} + cp -v /var/jenkins_config/*.yaml {{ .Values.controller.jenkinsHome }}/casc_configs + {{- end }} + {{- end }} + echo "finished initialization" +{{- if .Values.controller.initializeOnce }} + touch {{ .Values.controller.jenkinsHome }}/initialization-completed +{{- end }} + {{- if not .Values.controller.sidecars.configAutoReload.enabled }} +# Only add config to this script if we aren't auto-reloading otherwise the pod will restart upon each config change: +{{- if .Values.controller.JCasC.defaultConfig }} + jcasc-default-config.yaml: |- + {{- include "jenkins.casc.defaults" . |nindent 4}} +{{- end }} +{{- range $key, $val := .Values.controller.JCasC.configScripts }} + {{ $key }}.yaml: |- +{{ tpl $val $| indent 4 }} +{{- end }} +{{- end }} + plugins.txt: |- +{{- if .Values.controller.installPlugins }} + {{- range $installPlugin := .Values.controller.installPlugins }} + {{- $installPlugin | nindent 4 }} + {{- end }} + {{- range $addlPlugin := .Values.controller.additionalPlugins }} + {{- /* duplicate plugin check */}} + {{- range $installPlugin := $.Values.controller.installPlugins }} + {{- if eq (splitList ":" $addlPlugin | first) (splitList ":" $installPlugin | first) }} + {{- $message := print "[PLUGIN CONFLICT] controller.additionalPlugins contains '" $addlPlugin "'" }} + {{- $message := print $message " but controller.installPlugins already contains '" $installPlugin "'." }} + {{- $message := print $message " Override controller.installPlugins to use '" $addlPlugin "' plugin." }} + {{- fail $message }} + {{- end }} + {{- end }} + {{- $addlPlugin | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/deprecation.yaml b/charts/jenkins/jenkins/5.5.12/templates/deprecation.yaml new file mode 100644 index 0000000000..f54017ce4c --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/deprecation.yaml @@ -0,0 +1,151 @@ +{{- if .Values.checkDeprecation }} + {{- if .Values.master }} + {{ fail "`master` does no longer exist. It has been renamed to `controller`" }} + {{- end }} + + {{- if .Values.controller.imageTag }} + {{ fail "`controller.imageTag` does no longer exist. Please use `controller.image.tag` instead" }} + {{- end }} + + {{- if .Values.controller.slaveListenerPort }} + {{ fail "`controller.slaveListenerPort` does no longer exist. It has been renamed to `controller.agentListenerPort`" }} + {{- end }} + + {{- if .Values.controller.slaveHostPort }} + {{ fail "`controller.slaveHostPort` does no longer exist. It has been renamed to `controller.agentListenerHostPort`" }} + {{- end }} + + {{- if .Values.controller.slaveKubernetesNamespace }} + {{ fail "`controller.slaveKubernetesNamespace` does no longer exist. It has been renamed to `agent.namespace`" }} + {{- end }} + + {{- if .Values.controller.slaveDefaultsProviderTemplate }} + {{ fail "`controller.slaveDefaultsProviderTemplate` does no longer exist. It has been renamed to `agent.defaultsProviderTemplate`" }} + {{- end }} + + {{- if .Values.controller.useSecurity }} + {{ fail "`controller.useSecurity` does no longer exist. It has been renamed to `controller.adminSecret`" }} + {{- end }} + + {{- if .Values.controller.slaveJenkinsUrl }} + {{ fail "`controller.slaveJenkinsUrl` does no longer exist. It has been renamed to `agent.jenkinsUrl`" }} + {{- end }} + + {{- if .Values.controller.slaveJenkinsTunnel }} + {{ fail "`controller.slaveJenkinsTunnel` does no longer exist. It has been renamed to `agent.jenkinsTunnel`" }} + {{- end }} + + {{- if .Values.controller.slaveConnectTimeout }} + {{ fail "`controller.slaveConnectTimeout` does no longer exist. It has been renamed to `agent.kubernetesConnectTimeout`" }} + {{- end }} + + {{- if .Values.controller.slaveReadTimeout }} + {{ fail "`controller.slaveReadTimeout` does no longer exist. It has been renamed to `agent.kubernetesReadTimeout`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerServiceType }} + {{ fail "`controller.slaveListenerServiceType` does no longer exist. It has been renamed to `controller.agentListenerServiceType`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerLoadBalancerIP }} + {{ fail "`controller.slaveListenerLoadBalancerIP` does no longer exist. It has been renamed to `controller.agentListenerLoadBalancerIP`" }} + {{- end }} + + {{- if .Values.controller.slaveListenerServiceAnnotations }} + {{ fail "`controller.slaveListenerServiceAnnotations` does no longer exist. It has been renamed to `controller.agentListenerServiceAnnotations`" }} + {{- end }} + + {{- if .Values.agent.slaveConnectTimeout }} + {{ fail "`agent.slaveConnectTimeout` does no longer exist. It has been renamed to `agent.connectTimeout`" }} + {{- end }} + + {{- if .Values.NetworkPolicy }} + + {{- if .Values.NetworkPolicy.Enabled }} + {{ fail "`NetworkPolicy.Enabled` does no longer exist. It has been renamed to `networkPolicy.enabled`" }} + {{- end }} + + {{- if .Values.NetworkPolicy.ApiVersion }} + {{ fail "`NetworkPolicy.ApiVersion` does no longer exist. It has been renamed to `networkPolicy.apiVersion`" }} + {{- end }} + + {{ fail "NetworkPolicy.* values have been renamed, please check the documentation" }} + {{- end }} + + + {{- if .Values.rbac.install }} + {{ fail "`rbac.install` does no longer exist. It has been renamed to `rbac.create` and is enabled by default!" }} + {{- end }} + + {{- if .Values.rbac.serviceAccountName }} + {{ fail "`rbac.serviceAccountName` does no longer exist. It has been renamed to `serviceAccount.name`" }} + {{- end }} + + {{- if .Values.rbac.serviceAccountAnnotations }} + {{ fail "`rbac.serviceAccountAnnotations` does no longer exist. It has been renamed to `serviceAccount.annotations`" }} + {{- end }} + + {{- if .Values.rbac.roleRef }} + {{ fail "`rbac.roleRef` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.rbac.roleKind }} + {{ fail "`rbac.roleKind` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.rbac.roleBindingKind }} + {{ fail "`rbac.roleBindingKind` does no longer exist. RBAC roles are now generated, please check the documentation" }} + {{- end }} + + {{- if .Values.controller.JCasC.pluginVersion }} + {{ fail "controller.JCasC.pluginVersion has been deprecated, please use controller.installPlugins instead" }} + {{- end }} + + {{- if .Values.controller.deploymentLabels }} + {{ fail "`controller.deploymentLabels` does no longer exist. It has been renamed to `controller.statefulSetLabels`" }} + {{- end }} + + {{- if .Values.controller.deploymentAnnotations }} + {{ fail "`controller.deploymentAnnotations` does no longer exist. It has been renamed to `controller.statefulSetAnnotations`" }} + {{- end }} + + {{- if .Values.controller.rollingUpdate }} + {{ fail "`controller.rollingUpdate` does no longer exist. It is no longer relevant, since a StatefulSet is used for the Jenkins controller" }} + {{- end }} + + {{- if .Values.controller.tag }} + {{ fail "`controller.tag` no longer exists. It has been renamed to `controller.image.tag'" }} + {{- end }} + + {{- if .Values.controller.tagLabel }} + {{ fail "`controller.tagLabel` no longer exists. It has been renamed to `controller.image.tagLabel`" }} + {{- end }} + + {{- if .Values.controller.adminSecret }} + {{ fail "`controller.adminSecret` no longer exists. It has been renamed to `controller.admin.createSecret`" }} + {{- end }} + + {{- if .Values.controller.adminUser }} + {{ fail "`controller.adminUser` no longer exists. It has been renamed to `controller.admin.username`" }} + {{- end }} + + {{- if .Values.controller.adminPassword }} + {{ fail "`controller.adminPassword` no longer exists. It has been renamed to `controller.admin.password`" }} + {{- end }} + + {{- if .Values.controller.sidecars.other }} + {{ fail "`controller.sidecars.other` no longer exists. It has been renamed to `controller.sidecars.additionalSidecarContainers`" }} + {{- end }} + + {{- if .Values.agent.tag }} + {{ fail "`controller.agent.tag` no longer exists. It has been renamed to `controller.agent.image.tag`" }} + {{- end }} + + {{- if .Values.backup }} + {{ fail "`controller.backup` no longer exists." }} + {{- end }} + + {{- if .Values.helmtest.bats.tag }} + {{ fail "`helmtest.bats.tag` no longer exists. It has been renamed to `helmtest.bats.image.tag`" }} + {{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/home-pvc.yaml b/charts/jenkins/jenkins/5.5.12/templates/home-pvc.yaml new file mode 100644 index 0000000000..f417d23ad2 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/home-pvc.yaml @@ -0,0 +1,41 @@ +{{- if not (contains "jenkins-home" (quote .Values.persistence.volumes)) }} +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: +{{- if .Values.persistence.annotations }} + annotations: +{{ toYaml .Values.persistence.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.persistence.labels }} +{{ toYaml .Values.persistence.labels | indent 4 }} +{{- end }} +spec: +{{- if .Values.persistence.dataSource }} + dataSource: +{{ toYaml .Values.persistence.dataSource | indent 4 }} +{{- end }} + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- if .Values.persistence.storageClass }} +{{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jcasc-config.yaml b/charts/jenkins/jenkins/5.5.12/templates/jcasc-config.yaml new file mode 100644 index 0000000000..f514445256 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jcasc-config.yaml @@ -0,0 +1,53 @@ +{{- $root := . }} +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- range $key, $val := .Values.controller.JCasC.configScripts }} +{{- if $val }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.casc.configName" (list (printf "config-%s" $key) $ )}} + namespace: {{ template "jenkins.namespace" $root }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" $root}} + {{- if $root.Values.renderHelmLabels }} + "helm.sh/chart": "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" + {{ template "jenkins.fullname" $root }}-jenkins-config: "true" +{{- if $root.Values.controller.JCasC.configMapAnnotations }} + annotations: +{{ toYaml $root.Values.controller.JCasC.configMapAnnotations | indent 4 }} +{{- end }} +data: + {{ $key }}.yaml: |- +{{ tpl $val $| indent 4 }} +{{- end }} +{{- end }} +{{- if .Values.controller.JCasC.defaultConfig }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.casc.configName" (list "jcasc-config" $ )}} + namespace: {{ template "jenkins.namespace" $root }} + labels: + "app.kubernetes.io/name": {{ template "jenkins.name" $root}} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $.Release.Service }}" + "app.kubernetes.io/instance": "{{ $.Release.Name }}" + "app.kubernetes.io/component": "{{ $.Values.controller.componentName }}" + {{ template "jenkins.fullname" $root }}-jenkins-config: "true" +{{- if $root.Values.controller.JCasC.configMapAnnotations }} + annotations: +{{ toYaml $root.Values.controller.JCasC.configMapAnnotations | indent 4 }} +{{- end }} +data: + jcasc-default-config.yaml: |- + {{- include "jenkins.casc.defaults" . | nindent 4 }} +{{- end}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-agent-svc.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-agent-svc.yaml new file mode 100644 index 0000000000..4440b91f8f --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-agent-svc.yaml @@ -0,0 +1,43 @@ +{{- if .Values.controller.agentListenerEnabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jenkins.fullname" . }}-agent + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.agentListenerServiceAnnotations }} + annotations: + {{- toYaml .Values.controller.agentListenerServiceAnnotations | nindent 4 }} + {{- end }} +spec: + {{- if .Values.controller.agentListenerExternalTrafficPolicy }} + externalTrafficPolicy: {{.Values.controller.agentListenerExternalTrafficPolicy}} + {{- end }} + ports: + - port: {{ .Values.controller.agentListenerPort }} + targetPort: {{ .Values.controller.agentListenerPort }} + {{- if (and (eq .Values.controller.agentListenerServiceType "NodePort") (not (empty .Values.controller.agentListenerNodePort))) }} + nodePort: {{ .Values.controller.agentListenerNodePort }} + {{- end }} + name: agent-listener + selector: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + type: {{ .Values.controller.agentListenerServiceType }} + {{if eq .Values.controller.agentListenerServiceType "LoadBalancer"}} +{{- if .Values.controller.agentListenerLoadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.controller.agentListenerLoadBalancerSourceRanges | indent 4 }} +{{- end }} + {{- end }} + {{- if and (eq .Values.controller.agentListenerServiceType "LoadBalancer") (.Values.controller.agentListenerLoadBalancerIP) }} + loadBalancerIP: {{ .Values.controller.agentListenerLoadBalancerIP }} + {{- end }} + {{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-aws-security-group-policies.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-aws-security-group-policies.yaml new file mode 100644 index 0000000000..2f6e7a13d6 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-aws-security-group-policies.yaml @@ -0,0 +1,16 @@ +{{- if .Values.awsSecurityGroupPolicies.enabled -}} +{{- range .Values.awsSecurityGroupPolicies.policies -}} +apiVersion: vpcresources.k8s.aws/v1beta1 +kind: SecurityGroupPolicy +metadata: + name: {{ .name }} + namespace: {{ template "jenkins.namespace" $ }} +spec: + podSelector: + {{- toYaml .podSelector | nindent 6}} + securityGroups: + groupIds: + {{- toYaml .securityGroupIds | nindent 6}} +--- +{{- end -}} +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-alerting-rules.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-alerting-rules.yaml new file mode 100644 index 0000000000..3fd806172e --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-alerting-rules.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.controller.prometheus.enabled .Values.controller.prometheus.alertingrules }} +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.prometheus.prometheusRuleNamespace }} + namespace: {{ .Values.controller.prometheus.prometheusRuleNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.prometheus.alertingRulesAdditionalLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} +spec: + groups: +{{ toYaml .Values.controller.prometheus.alertingrules | indent 2 }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-backendconfig.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-backendconfig.yaml new file mode 100644 index 0000000000..0e8a566fc6 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-backendconfig.yaml @@ -0,0 +1,24 @@ +{{- if .Values.controller.backendconfig.enabled }} +apiVersion: {{ .Values.controller.backendconfig.apiVersion }} +kind: BackendConfig +metadata: + name: {{ .Values.controller.backendconfig.name }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.controller.backendconfig.labels }} +{{ toYaml .Values.controller.backendconfig.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.backendconfig.annotations }} + annotations: +{{ toYaml .Values.controller.backendconfig.annotations | indent 4 }} +{{- end }} +spec: +{{ toYaml .Values.controller.backendconfig.spec | indent 2 }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-ingress.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-ingress.yaml new file mode 100644 index 0000000000..b3b344ff87 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-ingress.yaml @@ -0,0 +1,77 @@ +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if .Values.controller.ingress.enabled }} +{{- if semverCompare ">=1.19-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.ingress.apiVersion }} +{{- end }} +kind: Ingress +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.controller.ingress.labels }} +{{ toYaml .Values.controller.ingress.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.ingress.annotations }} + annotations: +{{ toYaml .Values.controller.ingress.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} +spec: +{{- if .Values.controller.ingress.ingressClassName }} + ingressClassName: {{ .Values.controller.ingress.ingressClassName | quote }} +{{- end }} + rules: + - http: + paths: +{{- if empty (.Values.controller.ingress.paths) }} + - backend: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ template "jenkins.fullname" . }} + port: + number: {{ .Values.controller.servicePort }} + pathType: ImplementationSpecific +{{- else }} + serviceName: {{ template "jenkins.fullname" . }} + servicePort: {{ .Values.controller.servicePort }} +{{- end }} +{{- if .Values.controller.ingress.path }} + path: {{ .Values.controller.ingress.path }} +{{- end -}} +{{- else }} +{{ tpl (toYaml .Values.controller.ingress.paths | indent 6) . }} +{{- end -}} +{{- if .Values.controller.ingress.hostName }} + host: {{ tpl .Values.controller.ingress.hostName . | quote }} +{{- end }} +{{- if .Values.controller.ingress.resourceRootUrl }} + - http: + paths: + - backend: +{{- if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ template "jenkins.fullname" . }} + port: + number: {{ .Values.controller.servicePort }} + pathType: ImplementationSpecific +{{- else }} + serviceName: {{ template "jenkins.fullname" . }} + servicePort: {{ .Values.controller.servicePort }} +{{- end }} + host: {{ tpl .Values.controller.ingress.resourceRootUrl . | quote }} +{{- end }} +{{- if .Values.controller.ingress.tls }} + tls: +{{ tpl (toYaml .Values.controller.ingress.tls ) . | indent 4 }} +{{- end -}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-networkpolicy.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-networkpolicy.yaml new file mode 100644 index 0000000000..82835f2bda --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-networkpolicy.yaml @@ -0,0 +1,76 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ .Values.networkPolicy.apiVersion }} +metadata: + name: "{{ .Release.Name }}-{{ .Values.controller.componentName }}" + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +spec: + podSelector: + matchLabels: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + ingress: + # Allow web access to the UI + - ports: + - port: {{ .Values.controller.targetPort }} + {{- if .Values.controller.agentListenerEnabled }} + # Allow inbound connections from agents + - from: + {{- if .Values.networkPolicy.internalAgents.allowed }} + - podSelector: + matchLabels: + "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}": "true" + {{- range $k,$v:= .Values.networkPolicy.internalAgents.podLabels }} + {{ $k }}: {{ $v }} + {{- end }} + {{- if .Values.networkPolicy.internalAgents.namespaceLabels }} + namespaceSelector: + matchLabels: + {{- range $k,$v:= .Values.networkPolicy.internalAgents.namespaceLabels }} + {{ $k }}: {{ $v }} + {{- end }} + {{- end }} + {{- end }} + {{- if or .Values.networkPolicy.externalAgents.ipCIDR .Values.networkPolicy.externalAgents.except }} + - ipBlock: + cidr: {{ required "ipCIDR is required if you wish to allow external agents to connect to Jenkins Controller." .Values.networkPolicy.externalAgents.ipCIDR }} + {{- if .Values.networkPolicy.externalAgents.except }} + except: + {{- range .Values.networkPolicy.externalAgents.except }} + - {{ . }} + {{- end }} + {{- end }} + {{- end }} + ports: + - port: {{ .Values.controller.agentListenerPort }} + {{- end }} +{{- if .Values.agent.enabled }} +--- +kind: NetworkPolicy +apiVersion: {{ .Values.networkPolicy.apiVersion }} +metadata: + name: "{{ .Release.Name }}-{{ .Values.agent.componentName }}" + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +spec: + podSelector: + matchLabels: + # DefaultDeny + "jenkins/{{ .Release.Name }}-{{ .Values.agent.componentName }}": "true" +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-pdb.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-pdb.yaml new file mode 100644 index 0000000000..9dc1fafe26 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-pdb.yaml @@ -0,0 +1,34 @@ +{{- if .Values.controller.podDisruptionBudget.enabled }} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare ">=1.21-0" $kubeTargetVersion -}} +apiVersion: policy/v1 +{{- else if semverCompare ">=1.5-0" $kubeTargetVersion -}} +apiVersion: policy/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.podDisruptionBudget.apiVersion }} +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ template "jenkins.fullname" . }}-pdb + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.podDisruptionBudget.labels -}} + {{ toYaml .Values.controller.podDisruptionBudget.labels | nindent 4 }} + {{- end }} + {{- if .Values.controller.podDisruptionBudget.annotations }} + annotations: {{ toYaml .Values.controller.podDisruptionBudget.annotations | nindent 4 }} + {{- end }} +spec: + maxUnavailable: {{ .Values.controller.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-podmonitor.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-podmonitor.yaml new file mode 100644 index 0000000000..9a04019c36 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-podmonitor.yaml @@ -0,0 +1,30 @@ +{{- if .Values.controller.googlePodMonitor.enabled }} +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring + +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.googlePodMonitor.serviceMonitorNamespace }} + namespace: {{ .Values.controller.googlePodMonitor.serviceMonitorNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + +spec: + endpoints: + - interval: {{ .Values.controller.googlePodMonitor.scrapeInterval }} + port: http + path: {{ .Values.controller.jenkinsUriPrefix }}{{ .Values.controller.googlePodMonitor.scrapeEndpoint }} + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-route.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-route.yaml new file mode 100644 index 0000000000..3550380ee4 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-route.yaml @@ -0,0 +1,34 @@ +{{- if .Values.controller.route.enabled }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + app: {{ template "jenkins.fullname" . }} + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + component: "{{ .Release.Name }}-{{ .Values.controller.componentName }}" +{{- if .Values.controller.route.labels }} +{{ toYaml .Values.controller.route.labels | indent 4 }} +{{- end }} +{{- if .Values.controller.route.annotations }} + annotations: +{{ toYaml .Values.controller.route.annotations | indent 4 }} +{{- end }} + name: {{ template "jenkins.fullname" . }} +spec: + host: {{ .Values.controller.route.path }} + port: + targetPort: http + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + to: + kind: Service + name: {{ template "jenkins.fullname" . }} + weight: 100 + wildcardPolicy: None +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-secondary-ingress.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-secondary-ingress.yaml new file mode 100644 index 0000000000..c63e48229f --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-secondary-ingress.yaml @@ -0,0 +1,56 @@ +{{- if .Values.controller.secondaryingress.enabled }} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- $serviceName := include "jenkins.fullname" . -}} +{{- $servicePort := .Values.controller.servicePort -}} +{{- if semverCompare ">=1.19-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" $kubeTargetVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: {{ .Values.controller.secondaryingress.apiVersion }} +{{- end }} +kind: Ingress +metadata: + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.secondaryingress.labels -}} + {{ toYaml .Values.controller.secondaryingress.labels | nindent 4 }} + {{- end }} + {{- if .Values.controller.secondaryingress.annotations }} + annotations: {{ toYaml .Values.controller.secondaryingress.annotations | nindent 4 }} + {{- end }} + name: {{ template "jenkins.fullname" . }}-secondary +spec: +{{- if .Values.controller.secondaryingress.ingressClassName }} + ingressClassName: {{ .Values.controller.secondaryingress.ingressClassName | quote }} +{{- end }} + rules: + - host: {{ .Values.controller.secondaryingress.hostName }} + http: + paths: + {{- range .Values.controller.secondaryingress.paths }} + - path: {{ . | quote }} + backend: +{{ if semverCompare ">=1.19-0" $kubeTargetVersion }} + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + pathType: ImplementationSpecific +{{ else }} + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} +{{ end }} + {{- end}} +{{- if .Values.controller.secondaryingress.tls }} + tls: +{{ toYaml .Values.controller.secondaryingress.tls | indent 4 }} +{{- end -}} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-servicemonitor.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-servicemonitor.yaml new file mode 100644 index 0000000000..8710b2bc9c --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-servicemonitor.yaml @@ -0,0 +1,45 @@ +{{- if and .Values.controller.prometheus.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor + +metadata: + name: {{ template "jenkins.fullname" . }} +{{- if .Values.controller.prometheus.serviceMonitorNamespace }} + namespace: {{ .Values.controller.prometheus.serviceMonitorNamespace }} +{{- else }} + namespace: {{ template "jenkins.namespace" . }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.prometheus.serviceMonitorAdditionalLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + +spec: + endpoints: + - interval: {{ .Values.controller.prometheus.scrapeInterval }} + port: http + path: {{ .Values.controller.jenkinsUriPrefix }}{{ .Values.controller.prometheus.scrapeEndpoint }} + {{- with .Values.controller.prometheus.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.controller.prometheus.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + jobLabel: {{ template "jenkins.fullname" . }} + namespaceSelector: + matchNames: + - "{{ template "jenkins.namespace" $ }}" + selector: + matchLabels: + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-statefulset.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-statefulset.yaml new file mode 100644 index 0000000000..50e61acf14 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-statefulset.yaml @@ -0,0 +1,424 @@ +{{- if .Capabilities.APIVersions.Has "apps/v1" }} +apiVersion: apps/v1 +{{- else }} +apiVersion: apps/v1beta1 +{{- end }} +kind: StatefulSet +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.statefulSetLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + {{- if .Values.controller.statefulSetAnnotations }} + annotations: +{{ toYaml .Values.controller.statefulSetAnnotations | indent 4 }} + {{- end }} +spec: + serviceName: {{ template "jenkins.fullname" . }} + replicas: 1 + selector: + matchLabels: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + {{- if .Values.controller.updateStrategy }} + updateStrategy: +{{ toYaml .Values.controller.updateStrategy | indent 4 }} + {{- end }} + template: + metadata: + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- range $key, $val := .Values.controller.podLabels }} + {{ $key }}: {{ $val | quote }} + {{- end}} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }} + {{- if .Values.controller.initScripts }} + checksum/config-init-scripts: {{ include (print $.Template.BasePath "/config-init-scripts.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.controller.podAnnotations }} +{{ tpl (toYaml .Values.controller.podAnnotations | indent 8) . }} + {{- end }} + spec: + {{- if .Values.controller.schedulerName }} + schedulerName: {{ .Values.controller.schedulerName }} + {{- end }} + {{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 8 }} + {{- end }} + {{- if .Values.controller.affinity }} + affinity: +{{ toYaml .Values.controller.affinity | indent 8 }} + {{- end }} + {{- if .Values.controller.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.controller.topologySpreadConstraints | indent 8 }} + {{- end }} + {{- if quote .Values.controller.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.controller.priorityClassName }} + priorityClassName: {{ .Values.controller.priorityClassName }} + {{- end }} + {{- if .Values.controller.shareProcessNamespace }} + shareProcessNamespace: true + {{- end }} +{{- if .Values.controller.usePodSecurityContext }} + securityContext: + {{- if kindIs "map" .Values.controller.podSecurityContextOverride }} + {{- tpl (toYaml .Values.controller.podSecurityContextOverride | nindent 8) . -}} + {{- else }} + {{/* The rest of this section should be replaced with the contents of this comment one the runAsUser, fsGroup, and securityContextCapabilities Helm chart values have been removed: + runAsUser: 1000 + fsGroup: 1000 + runAsNonRoot: true + */}} + runAsUser: {{ default 0 .Values.controller.runAsUser }} + {{- if and (.Values.controller.runAsUser) (.Values.controller.fsGroup) }} + {{- if not (eq (int .Values.controller.runAsUser) 0) }} + fsGroup: {{ .Values.controller.fsGroup }} + runAsNonRoot: true + {{- end }} + {{- if .Values.controller.securityContextCapabilities }} + capabilities: + {{- toYaml .Values.controller.securityContextCapabilities | nindent 10 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} + serviceAccountName: "{{ template "jenkins.serviceAccountName" . }}" +{{- if .Values.controller.hostNetworking }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet +{{- end }} + {{- if .Values.controller.hostAliases }} + hostAliases: + {{- toYaml .Values.controller.hostAliases | nindent 8 }} + {{- end }} + initContainers: +{{- if .Values.controller.customInitContainers }} +{{ tpl (toYaml .Values.controller.customInitContainers) . | indent 8 }} +{{- end }} + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- include "jenkins.configReloadContainer" (list $ "config-reload-init" "init") | nindent 8 }} +{{- end}} + + - name: "init" + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + {{- if .Values.controller.containerSecurityContext }} + securityContext: {{- toYaml .Values.controller.containerSecurityContext | nindent 12 }} + {{- end }} + command: [ "sh", "/var/jenkins_config/apply_config.sh" ] + {{- if .Values.controller.initContainerEnvFrom }} + envFrom: +{{ (tpl (toYaml .Values.controller.initContainerEnvFrom) .) | indent 12 }} + {{- end }} + {{- if .Values.controller.initContainerEnv }} + env: +{{ (tpl (toYaml .Values.controller.initContainerEnv) .) | indent 12 }} + {{- end }} + resources: +{{- if .Values.controller.initContainerResources }} +{{ toYaml .Values.controller.initContainerResources | indent 12 }} +{{- else }} +{{ toYaml .Values.controller.resources | indent 12 }} +{{- end }} + volumeMounts: + {{- if .Values.persistence.mounts }} +{{ toYaml .Values.persistence.mounts | indent 12 }} + {{- end }} + - mountPath: {{ .Values.controller.jenkinsHome }} + name: jenkins-home + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - mountPath: /var/jenkins_config + name: jenkins-config + {{- if .Values.controller.installPlugins }} + {{- if .Values.controller.overwritePluginsFromImage }} + - mountPath: {{ .Values.controller.jenkinsRef }}/plugins + name: plugins + {{- end }} + - mountPath: /var/jenkins_plugins + name: plugin-dir + - mountPath: /tmp + name: tmp-volume + {{- end }} + {{- if or .Values.controller.initScripts .Values.controller.initConfigMap }} + - mountPath: {{ .Values.controller.jenkinsHome }}/init.groovy.d + name: init-scripts + {{- end }} + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + {{- $httpsJKSDirPath := printf "%s" .Values.controller.httpsKeyStore.path }} + - mountPath: {{ $httpsJKSDirPath }} + name: jenkins-https-keystore + {{- end }} + containers: + - name: jenkins + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + imagePullPolicy: "{{ .Values.controller.image.pullPolicy }}" + {{- if .Values.controller.containerSecurityContext }} + securityContext: {{- toYaml .Values.controller.containerSecurityContext | nindent 12 }} + {{- end }} + {{- if .Values.controller.overrideArgs }} + args: [ + {{- range $overrideArg := .Values.controller.overrideArgs }} + "{{- tpl $overrideArg $ }}", + {{- end }} + ] + {{- else if .Values.controller.httpsKeyStore.enable }} + {{- $httpsJKSFilePath := printf "%s/%s" .Values.controller.httpsKeyStore.path .Values.controller.httpsKeyStore.fileName }} + args: [ "--httpPort={{.Values.controller.httpsKeyStore.httpPort}}", "--httpsPort={{.Values.controller.targetPort}}", '--httpsKeyStore={{ $httpsJKSFilePath }}', "--httpsKeyStorePassword=$(JENKINS_HTTPS_KEYSTORE_PASSWORD)" ] + {{- else }} + args: [ "--httpPort={{.Values.controller.targetPort}}"] + {{- end }} + {{- if .Values.controller.lifecycle }} + lifecycle: +{{ toYaml .Values.controller.lifecycle | indent 12 }} + {{- end }} +{{- if .Values.controller.terminationMessagePath }} + terminationMessagePath: {{ .Values.controller.terminationMessagePath }} +{{- end }} +{{- if .Values.controller.terminationMessagePolicy }} + terminationMessagePolicy: {{ .Values.controller.terminationMessagePolicy }} +{{- end }} + {{- if .Values.controller.containerEnvFrom }} + envFrom: +{{ (tpl ( toYaml .Values.controller.containerEnvFrom) .) | indent 12 }} + {{- end }} + env: + {{- if .Values.controller.containerEnv }} +{{ (tpl ( toYaml .Values.controller.containerEnv) .) | indent 12 }} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: SECRETS + value: /run/secrets/additional + {{- end }} + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: JAVA_OPTS + value: >- + {{ if .Values.controller.sidecars.configAutoReload.enabled }} -Dcasc.reload.token=$(POD_NAME) {{ end }}{{ default "" .Values.controller.javaOpts }} + - name: JENKINS_OPTS + value: >- + {{ if .Values.controller.jenkinsUriPrefix }}--prefix={{ .Values.controller.jenkinsUriPrefix }} {{ end }} --webroot=/var/jenkins_cache/war {{ default "" .Values.controller.jenkinsOpts}} + - name: JENKINS_SLAVE_AGENT_PORT + value: "{{ .Values.controller.agentListenerPort }}" + {{- if .Values.controller.httpsKeyStore.enable }} + - name: JENKINS_HTTPS_KEYSTORE_PASSWORD + {{- if not .Values.controller.httpsKeyStore.disableSecretMount }} + valueFrom: + secretKeyRef: + name: {{ if .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretName }} {{ else if .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ else }} {{ template "jenkins.fullname" . }}-https-jks {{ end }} + key: "{{ .Values.controller.httpsKeyStore.jenkinsHttpsJksPasswordSecretKey }}" + {{- else }} + value: {{ .Values.controller.httpsKeyStore.password }} + {{- end }} + {{- end }} + + - name: CASC_JENKINS_CONFIG + value: {{ .Values.controller.sidecars.configAutoReload.folder | default (printf "%s/casc_configs" (.Values.controller.jenkinsRef)) }}{{- if .Values.controller.JCasC.configUrls }},{{ join "," .Values.controller.JCasC.configUrls }}{{- end }} + ports: + {{- if .Values.controller.httpsKeyStore.enable }} + - containerPort: {{.Values.controller.httpsKeyStore.httpPort}} + {{- else }} + - containerPort: {{.Values.controller.targetPort}} + {{- end }} + name: http + - containerPort: {{ .Values.controller.agentListenerPort }} + name: agent-listener + {{- if .Values.controller.agentListenerHostPort }} + hostPort: {{ .Values.controller.agentListenerHostPort }} + {{- end }} + {{- if .Values.controller.jmxPort }} + - containerPort: {{ .Values.controller.jmxPort }} + name: jmx + {{- end }} +{{- range $index, $port := .Values.controller.extraPorts }} + - containerPort: {{ $port.port }} + name: {{ $port.name }} +{{- end }} +{{- if and .Values.controller.healthProbes .Values.controller.probes}} + {{- if semverCompare ">=1.16-0" .Capabilities.KubeVersion.GitVersion }} + startupProbe: +{{ tpl (toYaml .Values.controller.probes.startupProbe | indent 12) .}} + {{- end }} + livenessProbe: +{{ tpl (toYaml .Values.controller.probes.livenessProbe | indent 12) .}} + readinessProbe: +{{ tpl (toYaml .Values.controller.probes.readinessProbe | indent 12) .}} +{{- end }} + resources: +{{ toYaml .Values.controller.resources | indent 12 }} + volumeMounts: +{{- if .Values.persistence.mounts }} +{{ toYaml .Values.persistence.mounts | indent 12 }} +{{- end }} + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + {{- $httpsJKSDirPath := printf "%s" .Values.controller.httpsKeyStore.path }} + - mountPath: {{ $httpsJKSDirPath }} + name: jenkins-https-keystore + {{- end }} + - mountPath: {{ .Values.controller.jenkinsHome }} + name: jenkins-home + readOnly: false + {{- if .Values.persistence.subPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + - mountPath: /var/jenkins_config + name: jenkins-config + readOnly: true + {{- if .Values.controller.installPlugins }} + - mountPath: {{ .Values.controller.jenkinsRef }}/plugins/ + name: plugin-dir + readOnly: false + {{- end }} + {{- if or .Values.controller.initScripts .Values.controller.initConfigMap }} + - mountPath: {{ .Values.controller.jenkinsHome }}/init.groovy.d + name: init-scripts + {{- end }} + {{- if .Values.controller.sidecars.configAutoReload.enabled }} + - name: sc-config-volume + mountPath: {{ .Values.controller.sidecars.configAutoReload.folder | default (printf "%s/casc_configs" (.Values.controller.jenkinsRef)) }} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: jenkins-secrets + mountPath: /run/secrets/additional + readOnly: true + {{- end }} + - name: jenkins-cache + mountPath: /var/jenkins_cache + - mountPath: /tmp + name: tmp-volume + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +{{- include "jenkins.configReloadContainer" (list $ "config-reload" "sidecar") | nindent 8 }} +{{- end}} + + +{{- if .Values.controller.sidecars.additionalSidecarContainers}} +{{ tpl (toYaml .Values.controller.sidecars.additionalSidecarContainers | indent 8) .}} +{{- end }} + + volumes: +{{- if .Values.persistence.volumes }} +{{ tpl (toYaml .Values.persistence.volumes | indent 6) . }} +{{- end }} + {{- if .Values.controller.sidecars.configAutoReload.logging.configuration.override }} + - name: auto-reload-config + configMap: + name: {{ template "jenkins.fullname" . }}-auto-reload-config + - name: auto-reload-config-logs + emptyDir: {} + {{- end }} + {{- if .Values.controller.installPlugins }} + {{- if .Values.controller.overwritePluginsFromImage }} + - name: plugins + emptyDir: {} + {{- end }} + {{- end }} + {{- if and .Values.controller.initScripts .Values.controller.initConfigMap }} + - name: init-scripts + projected: + sources: + - configMap: + name: {{ template "jenkins.fullname" . }}-init-scripts + - configMap: + name: {{ .Values.controller.initConfigMap }} + {{- else if .Values.controller.initConfigMap }} + - name: init-scripts + configMap: + name: {{ .Values.controller.initConfigMap }} + {{- else if .Values.controller.initScripts }} + - name: init-scripts + configMap: + name: {{ template "jenkins.fullname" . }}-init-scripts + {{- end }} + - name: jenkins-config + configMap: + name: {{ template "jenkins.fullname" . }} + {{- if .Values.controller.installPlugins }} + - name: plugin-dir + emptyDir: {} + {{- end }} + {{- if or .Values.controller.additionalSecrets .Values.controller.existingSecret .Values.controller.additionalExistingSecrets .Values.controller.admin.createSecret }} + - name: jenkins-secrets + projected: + sources: + {{- if .Values.controller.additionalSecrets }} + - secret: + name: {{ template "jenkins.fullname" . }}-additional-secrets + {{- end }} + {{- if .Values.controller.additionalExistingSecrets }} + {{- range $key, $value := .Values.controller.additionalExistingSecrets }} + - secret: + name: {{ tpl $value.name $ }} + items: + - key: {{ tpl $value.keyName $ }} + path: {{ tpl $value.name $ }}-{{ tpl $value.keyName $ }} + {{- end }} + {{- end }} + {{- if .Values.controller.admin.createSecret }} + - secret: + name: {{ .Values.controller.admin.existingSecret | default (include "jenkins.fullname" .) }} + items: + - key: {{ .Values.controller.admin.userKey | default "jenkins-admin-user" }} + path: chart-admin-username + - key: {{ .Values.controller.admin.passwordKey | default "jenkins-admin-password" }} + path: chart-admin-password + {{- end }} + {{- if .Values.controller.existingSecret }} + - secret: + name: {{ .Values.controller.existingSecret }} + {{- end }} + {{- end }} + - name: jenkins-cache + emptyDir: {} + {{- if not (contains "jenkins-home" (quote .Values.persistence.volumes)) }} + - name: jenkins-home + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim | default (include "jenkins.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end -}} + {{- end }} + - name: sc-config-volume + emptyDir: {} + - name: tmp-volume + emptyDir: {} + + {{- if and .Values.controller.httpsKeyStore.enable (not .Values.controller.httpsKeyStore.disableSecretMount) }} + - name: jenkins-https-keystore + secret: + secretName: {{ if .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName }} {{ else }} {{ template "jenkins.fullname" . }}-https-jks {{ end }} + items: + - key: {{ .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretKey }} + path: {{ .Values.controller.httpsKeyStore.fileName }} + {{- end }} + +{{- if .Values.controller.imagePullSecretName }} + imagePullSecrets: + - name: {{ .Values.controller.imagePullSecretName }} +{{- end -}} diff --git a/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-svc.yaml b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-svc.yaml new file mode 100644 index 0000000000..a83466ce3d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/jenkins-controller-svc.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + {{- if .Values.controller.serviceLabels }} +{{ toYaml .Values.controller.serviceLabels | indent 4 }} + {{- end }} +{{- if .Values.controller.serviceAnnotations }} + annotations: +{{ toYaml .Values.controller.serviceAnnotations | indent 4 }} +{{- end }} +spec: + {{- if .Values.controller.serviceExternalTrafficPolicy }} + externalTrafficPolicy: {{.Values.controller.serviceExternalTrafficPolicy}} + {{- end }} + {{- if (and (eq .Values.controller.serviceType "ClusterIP") (not (empty .Values.controller.clusterIP))) }} + clusterIP: {{.Values.controller.clusterIP}} + {{- end }} + ports: + - port: {{.Values.controller.servicePort}} + name: http + targetPort: {{ .Values.controller.targetPort }} + {{- if (and (eq .Values.controller.serviceType "NodePort") (not (empty .Values.controller.nodePort))) }} + nodePort: {{.Values.controller.nodePort}} + {{- end }} +{{- range $index, $port := .Values.controller.extraPorts }} + - port: {{ $port.port }} + name: {{ $port.name }} + {{- if $port.targetPort }} + targetPort: {{ $port.targetPort }} + {{- else }} + targetPort: {{ $port.port }} + {{- end -}} +{{- end }} + selector: + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + type: {{.Values.controller.serviceType}} + {{if eq .Values.controller.serviceType "LoadBalancer"}} +{{- if .Values.controller.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.controller.loadBalancerSourceRanges | indent 4 }} +{{- end }} + {{if .Values.controller.loadBalancerIP}} + loadBalancerIP: {{.Values.controller.loadBalancerIP}} + {{end}} + {{end}} diff --git a/charts/jenkins/jenkins/5.5.12/templates/rbac.yaml b/charts/jenkins/jenkins/5.5.12/templates/rbac.yaml new file mode 100644 index 0000000000..581cb8d48d --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/rbac.yaml @@ -0,0 +1,149 @@ +{{ if .Values.rbac.create }} +{{- $serviceName := include "jenkins.fullname" . -}} + +# This role is used to allow Jenkins scheduling of agents via Kubernetes plugin. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $serviceName }}-schedule-agents + namespace: {{ template "jenkins.agent.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: +- apiGroups: [""] + resources: ["pods", "pods/exec", "pods/log", "persistentvolumeclaims", "events"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["pods", "pods/exec", "persistentvolumeclaims"] + verbs: ["create", "delete", "deletecollection", "patch", "update"] + +--- + +# We bind the role to the Jenkins service account. The role binding is created in the namespace +# where the agents are supposed to run. +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-schedule-agents + namespace: {{ template "jenkins.agent.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $serviceName }}-schedule-agents +subjects: +- kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" .}} + namespace: {{ template "jenkins.namespace" . }} + +--- + +{{- if .Values.rbac.readSecrets }} +# This is needed if you want to use https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ +# as it needs permissions to get/watch/list Secrets +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "jenkins.fullname" . }}-read-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "watch", "list"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-read-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "jenkins.fullname" . }}-read-secrets +subjects: + - kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} + +--- +{{- end}} + +{{- if .Values.controller.sidecars.configAutoReload.enabled }} +# The sidecar container which is responsible for reloading configuration changes +# needs permissions to watch ConfigMaps +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "jenkins.fullname" . }}-casc-reload + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "watch", "list"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $serviceName }}-watch-configmaps + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "jenkins.fullname" . }}-casc-reload +subjects: +- kind: ServiceAccount + name: {{ template "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} + +{{- end}} + +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/secret-additional.yaml b/charts/jenkins/jenkins/5.5.12/templates/secret-additional.yaml new file mode 100644 index 0000000000..d1908aa9b3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/secret-additional.yaml @@ -0,0 +1,21 @@ +{{- if .Values.controller.additionalSecrets -}} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }}-additional-secrets + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: +{{- range .Values.controller.additionalSecrets }} + {{ .name }}: {{ .value | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/secret-claims.yaml b/charts/jenkins/jenkins/5.5.12/templates/secret-claims.yaml new file mode 100644 index 0000000000..e8b6d6c8e3 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/secret-claims.yaml @@ -0,0 +1,29 @@ +{{- if .Values.controller.secretClaims -}} +{{- $r := .Release -}} +{{- $v := .Values -}} +{{- $chart := printf "%s-%s" .Chart.Name .Chart.Version -}} +{{- $namespace := include "jenkins.namespace" . -}} +{{- $serviceName := include "jenkins.fullname" . -}} +{{ range .Values.controller.secretClaims }} +--- +kind: SecretClaim +apiVersion: vaultproject.io/v1 +metadata: + name: {{ $serviceName }}-{{ .name | default .path | lower }} + namespace: {{ $namespace }} + labels: + "app.kubernetes.io/name": '{{ $serviceName }}' + {{- if $v.renderHelmLabels }} + "helm.sh/chart": "{{ $chart }}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ $r.Service }}" + "app.kubernetes.io/instance": "{{ $r.Name }}" + "app.kubernetes.io/component": "{{ $v.controller.componentName }}" +spec: + type: {{ .type | default "Opaque" }} + path: {{ .path }} +{{- if .renew }} + renew: {{ .renew }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jenkins/jenkins/5.5.12/templates/secret-https-jks.yaml b/charts/jenkins/jenkins/5.5.12/templates/secret-https-jks.yaml new file mode 100644 index 0000000000..5348de41ee --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/secret-https-jks.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.controller.httpsKeyStore.enable ( not .Values.controller.httpsKeyStore.jenkinsHttpsJksSecretName ) (not .Values.controller.httpsKeyStore.disableSecretMount) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }}-https-jks + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: + jenkins-jks-file: | +{{ .Values.controller.httpsKeyStore.jenkinsKeyStoreBase64Encoded | indent 4 }} + https-jks-password: {{ .Values.controller.httpsKeyStore.password | b64enc }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/secret.yaml b/charts/jenkins/jenkins/5.5.12/templates/secret.yaml new file mode 100644 index 0000000000..cc6ace179e --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/secret.yaml @@ -0,0 +1,20 @@ +{{- if and (not .Values.controller.admin.existingSecret) (.Values.controller.admin.createSecret) -}} + +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "jenkins.fullname" . }} + namespace: {{ template "jenkins.namespace" . }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +type: Opaque +data: + jenkins-admin-password: {{ template "jenkins.password" . }} + jenkins-admin-user: {{ .Values.controller.admin.username | b64enc | quote }} +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/service-account-agent.yaml b/charts/jenkins/jenkins/5.5.12/templates/service-account-agent.yaml new file mode 100644 index 0000000000..48f08ba6c7 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/service-account-agent.yaml @@ -0,0 +1,26 @@ +{{ if .Values.serviceAccountAgent.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "jenkins.serviceAccountAgentName" . }} + namespace: {{ template "jenkins.agent.namespace" . }} +{{- if .Values.serviceAccountAgent.annotations }} + annotations: +{{ tpl (toYaml .Values.serviceAccountAgent.annotations) . | indent 4 }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.serviceAccountAgent.extraLabels }} +{{ tpl (toYaml .Values.serviceAccountAgent.extraLabels) . | indent 4 }} +{{- end }} +{{- if .Values.serviceAccountAgent.imagePullSecretName }} +imagePullSecrets: + - name: {{ .Values.serviceAccountAgent.imagePullSecretName }} +{{- end -}} +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/service-account.yaml b/charts/jenkins/jenkins/5.5.12/templates/service-account.yaml new file mode 100644 index 0000000000..b44eb488c5 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/service-account.yaml @@ -0,0 +1,26 @@ +{{ if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "jenkins.serviceAccountName" . }} + namespace: {{ template "jenkins.namespace" . }} +{{- if .Values.serviceAccount.annotations }} + annotations: +{{ tpl (toYaml .Values.serviceAccount.annotations) . | indent 4 }} +{{- end }} + labels: + "app.kubernetes.io/name": '{{ template "jenkins.name" .}}' + {{- if .Values.renderHelmLabels }} + "helm.sh/chart": "{{ template "jenkins.label" .}}" + {{- end }} + "app.kubernetes.io/managed-by": "{{ .Release.Service }}" + "app.kubernetes.io/instance": "{{ .Release.Name }}" + "app.kubernetes.io/component": "{{ .Values.controller.componentName }}" +{{- if .Values.serviceAccount.extraLabels }} +{{ tpl (toYaml .Values.serviceAccount.extraLabels) . | indent 4 }} +{{- end }} +{{- if .Values.serviceAccount.imagePullSecretName }} +imagePullSecrets: + - name: {{ .Values.serviceAccount.imagePullSecretName }} +{{- end -}} +{{ end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/tests/jenkins-test.yaml b/charts/jenkins/jenkins/5.5.12/templates/tests/jenkins-test.yaml new file mode 100644 index 0000000000..12a935ecc4 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/tests/jenkins-test.yaml @@ -0,0 +1,49 @@ +{{- if .Values.controller.testEnabled }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-ui-test-{{ randAlphaNum 5 | lower }}" + namespace: {{ template "jenkins.namespace" . }} + annotations: + "helm.sh/hook": test-success +spec: + {{- if .Values.controller.nodeSelector }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 4 }} + {{- end }} + {{- if .Values.controller.tolerations }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 4 }} + {{- end }} + initContainers: + - name: "test-framework" + image: "{{ .Values.helmtest.bats.image.registry }}/{{ .Values.helmtest.bats.image.repository }}:{{ .Values.helmtest.bats.image.tag }}" + command: + - "bash" + - "-c" + args: + - | + # copy bats to tools dir + set -ex + cp -R /opt/bats /tools/bats/ + volumeMounts: + - mountPath: /tools + name: tools + containers: + - name: {{ .Release.Name }}-ui-test + image: "{{ .Values.controller.image.registry }}/{{ .Values.controller.image.repository }}:{{- include "controller.image.tag" . -}}" + command: ["/tools/bats/bin/bats", "-t", "/tests/run.sh"] + volumeMounts: + - mountPath: /tests + name: tests + readOnly: true + - mountPath: /tools + name: tools + volumes: + - name: tests + configMap: + name: {{ template "jenkins.fullname" . }}-tests + - name: tools + emptyDir: {} + restartPolicy: Never +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/templates/tests/test-config.yaml b/charts/jenkins/jenkins/5.5.12/templates/tests/test-config.yaml new file mode 100644 index 0000000000..12c5b3a0d7 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/templates/tests/test-config.yaml @@ -0,0 +1,14 @@ +{{- if .Values.controller.testEnabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "jenkins.fullname" . }}-tests + namespace: {{ template "jenkins.namespace" . }} + annotations: + "helm.sh/hook": test +data: + run.sh: |- + @test "Testing Jenkins UI is accessible" { + curl --retry 48 --retry-delay 10 {{ template "jenkins.fullname" . }}:{{ .Values.controller.servicePort }}{{ default "" .Values.controller.jenkinsUriPrefix }}/login + } +{{- end }} diff --git a/charts/jenkins/jenkins/5.5.12/values.yaml b/charts/jenkins/jenkins/5.5.12/values.yaml new file mode 100644 index 0000000000..d2dc95f837 --- /dev/null +++ b/charts/jenkins/jenkins/5.5.12/values.yaml @@ -0,0 +1,1357 @@ +# Default values for jenkins. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value + +## Overrides for generated resource names +# See templates/_helpers.tpl +# -- Override the resource name prefix +# @default -- `Chart.Name` +nameOverride: +# -- Override the full resource names +# @default -- `jenkins-(release-name)` or `jenkins` if the release-name is `jenkins` +fullnameOverride: +# -- Override the deployment namespace +# @default -- `Release.Namespace` +namespaceOverride: + +# For FQDN resolving of the controller service. Change this value to match your existing configuration. +# ref: https://github.com/kubernetes/dns/blob/master/docs/specification.md +# -- Override the cluster name for FQDN resolving +clusterZone: "cluster.local" + +# -- The URL of the Kubernetes API server +kubernetesURL: "https://kubernetes.default" + +# -- The Jenkins credentials to access the Kubernetes API server. For the default cluster it is not needed. +credentialsId: + +# -- Enables rendering of the helm.sh/chart label to the annotations +renderHelmLabels: true + +controller: + # -- Used for label app.kubernetes.io/component + componentName: "jenkins-controller" + image: + # -- Controller image registry + registry: "docker.io" + # -- Controller image repository + repository: "jenkins/jenkins" + + # -- Controller image tag override; i.e., tag: "2.440.1-jdk17" + tag: + + # -- Controller image tag label + tagLabel: jdk17 + # -- Controller image pull policy + pullPolicy: "Always" + # -- Controller image pull secret + imagePullSecretName: + # -- Lifecycle specification for controller-container + lifecycle: {} + # postStart: + # exec: + # command: + # - "uname" + # - "-a" + + # -- Disable use of remember me + disableRememberMe: false + + # -- Set Number of executors + numExecutors: 0 + + # -- Sets the executor mode of the Jenkins node. Possible values are "NORMAL" or "EXCLUSIVE" + executorMode: "NORMAL" + + # -- Append Jenkins labels to the controller + customJenkinsLabels: [] + + hostNetworking: false + + # When enabling LDAP or another non-Jenkins identity source, the built-in admin account will no longer exist. + # If you disable the non-Jenkins identity store and instead use the Jenkins internal one, + # you should revert controller.admin.username to your preferred admin user: + admin: + + # -- Admin username created as a secret if `controller.admin.createSecret` is true + username: "admin" + # -- Admin password created as a secret if `controller.admin.createSecret` is true + # @default -- + password: + + # -- The key in the existing admin secret containing the username + userKey: jenkins-admin-user + # -- The key in the existing admin secret containing the password + passwordKey: jenkins-admin-password + + # The default configuration uses this secret to configure an admin user + # If you don't need that user or use a different security realm, then you can disable it + # -- Create secret for admin user + createSecret: true + + # -- The name of an existing secret containing the admin credentials + existingSecret: "" + # -- Email address for the administrator of the Jenkins instance + jenkinsAdminEmail: + + # This value should not be changed unless you use your custom image of jenkins or any derived from. + # If you want to use Cloudbees Jenkins Distribution docker, you should set jenkinsHome: "/var/cloudbees-jenkins-distribution" + # -- Custom Jenkins home path + jenkinsHome: "/var/jenkins_home" + + # This value should not be changed unless you use your custom image of jenkins or any derived from. + # If you want to use Cloudbees Jenkins Distribution docker, you should set jenkinsRef: "/usr/share/cloudbees-jenkins-distribution/ref" + # -- Custom Jenkins reference path + jenkinsRef: "/usr/share/jenkins/ref" + + # Path to the jenkins war file which is used by jenkins-plugin-cli. + jenkinsWar: "/usr/share/jenkins/jenkins.war" + # Override the default arguments passed to the war + # overrideArgs: + # - --httpPort=8080 + + # -- Resource allocation (Requests and Limits) + resources: + requests: + cpu: "50m" + memory: "256Mi" + limits: + cpu: "2000m" + memory: "4096Mi" + + # Share process namespace to allow sidecar containers to interact with processes in other containers in the same pod + shareProcessNamespace: false + + # Overrides the init container default values + # -- Resources allocation (Requests and Limits) for Init Container + initContainerResources: {} + # initContainerResources: + # requests: + # cpu: "50m" + # memory: "256Mi" + # limits: + # cpu: "2000m" + # memory: "4096Mi" + # -- Environment variable sources for Init Container + initContainerEnvFrom: [] + + # useful for i.e., http_proxy + # -- Environment variables for Init Container + initContainerEnv: [] + # initContainerEnv: + # - name: http_proxy + # value: "http://192.168.64.1:3128" + + # -- Environment variable sources for Jenkins Container + containerEnvFrom: [] + + # -- Environment variables for Jenkins Container + containerEnv: [] + # - name: http_proxy + # value: "http://192.168.64.1:3128" + + # Set min/max heap here if needed with "-Xms512m -Xmx512m" + # -- Append to `JAVA_OPTS` env var + javaOpts: + # -- Append to `JENKINS_OPTS` env var + jenkinsOpts: + + # If you are using the ingress definitions provided by this chart via the `controller.ingress` block, + # the configured hostname will be the ingress hostname starting with `https://` + # or `http://` depending on the `tls` configuration. + # The Protocol can be overwritten by specifying `controller.jenkinsUrlProtocol`. + # -- Set protocol for Jenkins URL; `https` if `controller.ingress.tls`, `http` otherwise + jenkinsUrlProtocol: + + # -- Set Jenkins URL if you are not using the ingress definitions provided by the chart + jenkinsUrl: + + # If you set this prefix and use ingress controller, then you might want to set the ingress path below + # I.e., "/jenkins" + # -- Root URI Jenkins will be served on + jenkinsUriPrefix: + + # -- Enable pod security context (must be `true` if podSecurityContextOverride, runAsUser or fsGroup are set) + usePodSecurityContext: true + + # Note that `runAsUser`, `fsGroup`, and `securityContextCapabilities` are + # being deprecated and replaced by `podSecurityContextOverride`. + # Set runAsUser to 1000 to let Jenkins run as non-root user 'jenkins', which exists in 'jenkins/jenkins' docker image. + # When configuring runAsUser to a different value than 0 also set fsGroup to the same value: + # -- Deprecated in favor of `controller.podSecurityContextOverride`. uid that jenkins runs with. + runAsUser: 1000 + + # -- Deprecated in favor of `controller.podSecurityContextOverride`. uid that will be used for persistent volume. + fsGroup: 1000 + + # If you have PodSecurityPolicies that require dropping of capabilities as suggested by CIS K8s benchmark, put them here + # securityContextCapabilities: + # drop: + # - NET_RAW + securityContextCapabilities: {} + + # In the case of mounting an ext4 filesystem, it might be desirable to use `supplementalGroups` instead of `fsGroup` in + # the `securityContext` block: https://github.com/kubernetes/kubernetes/issues/67014#issuecomment-589915496 + # podSecurityContextOverride: + # runAsUser: 1000 + # runAsNonRoot: true + # supplementalGroups: [1000] + # capabilities: {} + # -- Completely overwrites the contents of the pod security context, ignoring the values provided for `runAsUser`, `fsGroup`, and `securityContextCapabilities` + podSecurityContextOverride: ~ + + # -- Allow controlling the securityContext for the jenkins container + containerSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + + # For minikube, set this to NodePort, elsewhere uses LoadBalancer + # Use ClusterIP if your setup includes ingress controller + # -- k8s service type + serviceType: ClusterIP + + # -- k8s service clusterIP. Only used if serviceType is ClusterIP + clusterIp: + # -- k8s service port + servicePort: 8080 + # -- k8s target port + targetPort: 8080 + # -- k8s node port. Only used if serviceType is NodePort + nodePort: + + # Use Local to preserve the client source IP and avoids a second hop for LoadBalancer and NodePort type services, + # but risks potentially imbalanced traffic spreading. + serviceExternalTrafficPolicy: + + # -- Jenkins controller service annotations + serviceAnnotations: {} + # -- Jenkins controller custom labels for the StatefulSet + statefulSetLabels: {} + # foo: bar + # bar: foo + # -- Labels for the Jenkins controller-service + serviceLabels: {} + # service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https + + # Put labels on Jenkins controller pod + # -- Custom Pod labels (an object with `label-key: label-value` pairs) + podLabels: {} + + # Enable Kubernetes Startup, Liveness and Readiness Probes + # if Startup Probe is supported, enable it too + # ~ 2 minutes to allow Jenkins to restart when upgrading plugins. Set ReadinessTimeout to be shorter than LivenessTimeout. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes + # -- Enable Kubernetes Probes configuration configured in `controller.probes` + healthProbes: true + + probes: + startupProbe: + # -- Set the failure threshold for the startup probe + failureThreshold: 12 + httpGet: + # -- Set the Pod's HTTP path for the startup probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the startup probe + port: http + # -- Set the time interval between two startup probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the startup probe in seconds + timeoutSeconds: 5 + + livenessProbe: + # -- Set the failure threshold for the liveness probe + failureThreshold: 5 + httpGet: + # -- Set the Pod's HTTP path for the liveness probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the liveness probe + port: http + # -- Set the time interval between two liveness probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the liveness probe in seconds + timeoutSeconds: 5 + + # If Startup Probe is not supported on your Kubernetes cluster, you might want to use "initialDelaySeconds" instead. + # It delays the initial liveness probe while Jenkins is starting + # -- Set the initial delay for the liveness probe in seconds + initialDelaySeconds: + + readinessProbe: + # -- Set the failure threshold for the readiness probe + failureThreshold: 3 + httpGet: + # -- Set the Pod's HTTP path for the liveness probe + path: '{{ default "" .Values.controller.jenkinsUriPrefix }}/login' + # -- Set the Pod's HTTP port to use for the readiness probe + port: http + # -- Set the time interval between two readiness probes executions in seconds + periodSeconds: 10 + # -- Set the timeout for the readiness probe in seconds + timeoutSeconds: 5 + + # If Startup Probe is not supported on your Kubernetes cluster, you might want to use "initialDelaySeconds" instead. + # It delays the initial readiness probe while Jenkins is starting + # -- Set the initial delay for the readiness probe in seconds + initialDelaySeconds: + + # PodDisruptionBudget config + podDisruptionBudget: + # ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + + # -- Enable Kubernetes Pod Disruption Budget configuration + enabled: false + + # For Kubernetes v1.5+, use 'policy/v1beta1' + # For Kubernetes v1.21+, use 'policy/v1' + # -- Policy API version + apiVersion: "policy/v1beta1" + + annotations: {} + labels: {} + # -- Number of pods that can be unavailable. Either an absolute number or a percentage + maxUnavailable: "0" + + # -- Create Agent listener service + agentListenerEnabled: true + # -- Listening port for agents + agentListenerPort: 50000 + # -- Host port to listen for agents + agentListenerHostPort: + # -- Node port to listen for agents + agentListenerNodePort: + + # ref: https://kubernetes.io/docs/concepts/services-networking/service/#traffic-policies + # -- Traffic Policy of for the agentListener service + agentListenerExternalTrafficPolicy: + # -- Allowed inbound IP for the agentListener service + agentListenerLoadBalancerSourceRanges: + - 0.0.0.0/0 + # -- Disabled agent protocols + disabledAgentProtocols: + - JNLP-connect + - JNLP2-connect + csrf: + defaultCrumbIssuer: + # -- Enable the default CSRF Crumb issuer + enabled: true + # -- Enable proxy compatibility + proxyCompatability: true + + # Kubernetes service type for the JNLP agent service + # agentListenerServiceType is the Kubernetes Service type for the JNLP agent service, + # either 'LoadBalancer', 'NodePort', or 'ClusterIP' + # Note if you set this to 'LoadBalancer', you *must* define annotations to secure it. By default, + # this will be an external load balancer and allowing inbound 0.0.0.0/0, a HUGE + # security risk: https://github.com/kubernetes/charts/issues/1341 + # -- Defines how to expose the agentListener service + agentListenerServiceType: "ClusterIP" + + # -- Annotations for the agentListener service + agentListenerServiceAnnotations: {} + + # Optionally, assign an IP to the LoadBalancer agentListenerService LoadBalancer + # GKE users: only regional static IPs will work for Service Load balancer. + # -- Static IP for the agentListener LoadBalancer + agentListenerLoadBalancerIP: + + # -- Whether legacy remoting security should be enabled + legacyRemotingSecurityEnabled: false + + # Example of a 'LoadBalancer'-type agent listener with annotations securing it + # agentListenerServiceType: LoadBalancer + # agentListenerServiceAnnotations: + # service.beta.kubernetes.io/aws-load-balancer-internal: "True" + # service.beta.kubernetes.io/load-balancer-source-ranges: "172.0.0.0/8, 10.0.0.0/8" + + # LoadBalancerSourcesRange is a list of allowed CIDR values, which are combined with ServicePort to + # set allowed inbound rules on the security group assigned to the controller load balancer + # -- Allowed inbound IP addresses + loadBalancerSourceRanges: + - 0.0.0.0/0 + + # -- Optionally assign a known public LB IP + loadBalancerIP: + + # Optionally configure a JMX port. This requires additional javaOpts, for example, + # javaOpts: > + # -Dcom.sun.management.jmxremote.port=4000 + # -Dcom.sun.management.jmxremote.authenticate=false + # -Dcom.sun.management.jmxremote.ssl=false + # jmxPort: 4000 + # -- Open a port, for JMX stats + jmxPort: + + # -- Optionally configure other ports to expose in the controller container + extraPorts: [] + # - name: BuildInfoProxy + # port: 9000 + # targetPort: 9010 (Optional: Use to explicitly set targetPort if different from port) + + # Plugins will be installed during Jenkins controller start + # -- List of Jenkins plugins to install. If you don't want to install plugins, set it to `false` + installPlugins: + - kubernetes:4285.v50ed5f624918 + - workflow-aggregator:600.vb_57cdd26fdd7 + - git:5.4.1 + - configuration-as-code:1850.va_a_8c31d3158b_ + + # If set to false, Jenkins will download the minimum required version of all dependencies. + # -- Download the minimum required version or latest version of all dependencies + installLatestPlugins: true + + # -- Set to true to download the latest version of any plugin that is requested to have the latest version + installLatestSpecifiedPlugins: false + + # -- List of plugins to install in addition to those listed in controller.installPlugins + additionalPlugins: [] + + # Without this; whenever the controller gets restarted (Evicted, etc.) it will fetch plugin updates that have the potential to cause breakage. + # Note that for this to work, `persistence.enabled` needs to be set to `true` + # -- Initialize only on first installation. Ensures plugins do not get updated inadvertently. Requires `persistence.enabled` to be set to `true` + initializeOnce: false + + # Enable to always override the installed plugins with the values of 'controller.installPlugins' on upgrade or redeployment. + # -- Overwrite installed plugins on start + overwritePlugins: false + + # Configures if plugins bundled with `controller.image` should be overwritten with the values of 'controller.installPlugins' on upgrade or redeployment. + # -- Overwrite plugins that are already installed in the controller image + overwritePluginsFromImage: true + + # Configures the restrictions for naming projects. Set this key to null or empty to skip it in the default config. + projectNamingStrategy: standard + + # Useful with ghprb plugin. The OWASP plugin is not installed by default, please update controller.installPlugins. + # -- Enable HTML parsing using OWASP Markup Formatter Plugin (antisamy-markup-formatter) + enableRawHtmlMarkupFormatter: false + + # This is ignored if enableRawHtmlMarkupFormatter is true + # -- Yaml of the markup formatter to use + markupFormatter: plainText + + # Used to approve a list of groovy functions in pipelines used the script-security plugin. Can be viewed under /scriptApproval + # -- List of groovy functions to approve + scriptApproval: [] + # - "method groovy.json.JsonSlurperClassic parseText java.lang.String" + # - "new groovy.json.JsonSlurperClassic" + + # -- Map of groovy init scripts to be executed during Jenkins controller start + initScripts: {} + # test: |- + # print 'adding global pipeline libraries, register properties, bootstrap jobs...' + # -- Name of the existing ConfigMap that contains init scripts + initConfigMap: + + # 'name' is a name of an existing secret in the same namespace as jenkins, + # 'keyName' is the name of one of the keys inside the current secret. + # the 'name' and 'keyName' are concatenated with a '-' in between, so for example: + # an existing secret "secret-credentials" and a key inside it named "github-password" should be used in JCasC as ${secret-credentials-github-password} + # 'name' and 'keyName' must be lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', + # and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc') + # existingSecret existing secret "secret-credentials" and a key inside it named "github-username" should be used in JCasC as ${github-username} + # When using existingSecret no need to specify the keyName under additionalExistingSecrets. + existingSecret: + + # -- List of additional existing secrets to mount + additionalExistingSecrets: [] + # ref: https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets + # additionalExistingSecrets: + # - name: secret-name-1 + # keyName: username + # - name: secret-name-1 + # keyName: password + + # -- List of additional secrets to create and mount + additionalSecrets: [] + # ref: https://github.com/jenkinsci/configuration-as-code-plugin/blob/master/docs/features/secrets.adoc#kubernetes-secrets + # additionalSecrets: + # - name: nameOfSecret + # value: secretText + + # Generate SecretClaim resources to create Kubernetes secrets from HashiCorp Vault using kube-vault-controller. + # 'name' is the name of the secret that will be created in Kubernetes. The Jenkins fullname is prepended to this value. + # 'path' is the fully qualified path to the secret in Vault + # 'type' is an optional Kubernetes secret type. The default is 'Opaque' + # 'renew' is an optional secret renewal time in seconds + # -- List of `SecretClaim` resources to create + secretClaims: [] + # - name: secretName # required + # path: testPath # required + # type: kubernetes.io/tls # optional + # renew: 60 # optional + + # -- Name of default cloud configuration. + cloudName: "kubernetes" + + # Below is the implementation of Jenkins Configuration as Code. Add a key under configScripts for each configuration area, + # where each corresponds to a plugin or section of the UI. Each key (prior to | character) is just a label, and can be any value. + # Keys are only used to give the section a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label + # characters: lowercase letters, numbers, and hyphens. The keys become the name of a configuration yaml file on the controller in + # /var/jenkins_home/casc_configs (by default) and will be processed by the Configuration as Code Plugin. The lines after each | + # become the content of the configuration yaml file. The first line after this is a JCasC root element, e.g., jenkins, credentials, + # etc. Best reference is https:///configuration-as-code/reference. The example below creates a welcome message: + JCasC: + # -- Enables default Jenkins configuration via configuration as code plugin + defaultConfig: true + + # If true, the init container deletes all the plugin config files and Jenkins Config as Code overwrites any existing configuration + # -- Whether Jenkins Config as Code should overwrite any existing configuration + overwriteConfiguration: false + # -- Remote URLs for configuration files. + configUrls: [] + # - https://acme.org/jenkins.yaml + # -- List of Jenkins Config as Code scripts + configScripts: {} + # welcome-message: | + # jenkins: + # systemMessage: Welcome to our CI\CD server. This Jenkins is configured and managed 'as code'. + + # Allows adding to the top-level security JCasC section. For legacy purposes, by default, the chart includes apiToken configurations + # -- Jenkins Config as Code security-section + security: + apiToken: + creationOfLegacyTokenEnabled: false + tokenGenerationOnCreationEnabled: false + usageStatisticsEnabled: true + + # Ignored if securityRealm is defined in controller.JCasC.configScripts + # -- Jenkins Config as Code Security Realm-section + securityRealm: |- + local: + allowsSignup: false + enableCaptcha: false + users: + - id: "${chart-admin-username}" + name: "Jenkins Admin" + password: "${chart-admin-password}" + + # Ignored if authorizationStrategy is defined in controller.JCasC.configScripts + # -- Jenkins Config as Code Authorization Strategy-section + authorizationStrategy: |- + loggedInUsersCanDoAnything: + allowAnonymousRead: false + + # -- Annotations for the JCasC ConfigMap + configMapAnnotations: {} + + # -- Custom init-container specification in raw-yaml format + customInitContainers: [] + # - name: custom-init + # image: "alpine:3" + # imagePullPolicy: Always + # command: [ "uname", "-a" ] + + sidecars: + configAutoReload: + # If enabled: true, Jenkins Configuration as Code will be reloaded on-the-fly without a reboot. + # If false or not-specified, JCasC changes will cause a reboot and will only be applied at the subsequent start-up. + # Auto-reload uses the http:///reload-configuration-as-code endpoint to reapply config when changes to + # the configScripts are detected. + # -- Enables Jenkins Config as Code auto-reload + enabled: true + image: + # -- Registry for the image that triggers the reload + registry: docker.io + # -- Repository of the image that triggers the reload + repository: kiwigrid/k8s-sidecar + # -- Tag for the image that triggers the reload + tag: 1.27.5 + imagePullPolicy: IfNotPresent + resources: {} + # limits: + # cpu: 100m + # memory: 100Mi + # requests: + # cpu: 50m + # memory: 50Mi + # -- Enables additional volume mounts for the config auto-reload container + additionalVolumeMounts: [] + # - name: auto-reload-config + # mountPath: /var/config/logger + # - name: auto-reload-logs + # mountPath: /var/log/auto_reload + # -- Config auto-reload logging settings + logging: + # See default settings https://github.com/kiwigrid/k8s-sidecar/blob/master/src/logger.py + configuration: + # -- Enables custom log config utilizing using the settings below. + override: false + logLevel: INFO + formatter: JSON + logToConsole: true + logToFile: false + maxBytes: 1024 + backupCount: 3 + + # -- The scheme to use when connecting to the Jenkins configuration as code endpoint + scheme: http + # -- Skip TLS verification when connecting to the Jenkins configuration as code endpoint + skipTlsVerify: false + + # -- How many connection-related errors to retry on + reqRetryConnect: 10 + # -- How many seconds to wait before updating config-maps/secrets (sets METHOD=SLEEP on the sidecar) + sleepTime: + + # -- Environment variable sources for the Jenkins Config as Code auto-reload container + envFrom: [] + # -- Environment variables for the Jenkins Config as Code auto-reload container + env: {} + # - name: REQ_TIMEOUT + # value: "30" + + # SSH port value can be set to any unused TCP port. The default, 1044, is a non-standard SSH port that has been chosen at random. + # This is only used to reload JCasC config from the sidecar container running in the Jenkins controller pod. + # This TCP port will not be open in the pod (unless you specifically configure this), so Jenkins will not be + # accessible via SSH from outside the pod. Note if you use non-root pod privileges (runAsUser & fsGroup), + # this must be > 1024: + sshTcpPort: 1044 + # folder in the pod that should hold the collected dashboards: + folder: "/var/jenkins_home/casc_configs" + + # If specified, the sidecar will search for JCasC config-maps inside this namespace. + # Otherwise, the namespace in which the sidecar is running will be used. + # It's also possible to specify ALL to search in all namespaces: + # searchNamespace: + # -- Enable container security context + containerSecurityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + + # -- Configures additional sidecar container(s) for the Jenkins controller + additionalSidecarContainers: [] + ## The example below runs the client for https://smee.io as sidecar container next to Jenkins, + ## that allows triggering build behind a secure firewall. + ## https://jenkins.io/blog/2019/01/07/webhook-firewalls/#triggering-builds-with-webhooks-behind-a-secure-firewall + ## + ## Note: To use it you should go to https://smee.io/new and update the url to the generated one. + # - name: smee + # image: docker.io/twalter/smee-client:1.0.2 + # args: ["--port", "{{ .Values.controller.servicePort }}", "--path", "/github-webhook/", "--url", "https://smee.io/new"] + # resources: + # limits: + # cpu: 50m + # memory: 128Mi + # requests: + # cpu: 10m + # memory: 32Mi + + # -- Name of the Kubernetes scheduler to use + schedulerName: "" + + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # -- Node labels for pod assignment + nodeSelector: {} + + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + # -- Toleration labels for pod assignment + tolerations: [] + # -- Set TerminationGracePeriodSeconds + terminationGracePeriodSeconds: + # -- Set the termination message path + terminationMessagePath: + # -- Set the termination message policy + terminationMessagePolicy: + + # -- Affinity settings + affinity: {} + + # Leverage a priorityClass to ensure your pods survive resource shortages + # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + # -- The name of a `priorityClass` to apply to the controller pod + priorityClassName: + + # -- Annotations for controller pod + podAnnotations: {} + # -- Annotations for controller StatefulSet + statefulSetAnnotations: {} + + # ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + # -- Update strategy for StatefulSet + updateStrategy: {} + + # -- Topology spread constraints + topologySpreadConstraints: {} + + ingress: + # -- Enables ingress + enabled: false + + # Override for the default paths that map requests to the backend + # -- Override for the default Ingress paths + paths: [] + # - backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + # - backend: + # serviceName: >- + # {{ template "jenkins.fullname" . }} + # # Don't use string here, use only integer value! + # servicePort: 8080 + + # For Kubernetes v1.14+, use 'networking.k8s.io/v1beta1' + # For Kubernetes v1.19+, use 'networking.k8s.io/v1' + # -- Ingress API version + apiVersion: "extensions/v1beta1" + # -- Ingress labels + labels: {} + # -- Ingress annotations + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName + # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress + # ingressClassName: nginx + + # Set this path to jenkinsUriPrefix above or use annotations to rewrite path + # -- Ingress path + path: + + # configures the hostname e.g. jenkins.example.com + # -- Ingress hostname + hostName: + # -- Hostname to serve assets from + resourceRootUrl: + # -- Ingress TLS configuration + tls: [] + # - secretName: jenkins.cluster.local + # hosts: + # - jenkins.cluster.local + + # often you want to have your controller all locked down and private, + # but you still want to get webhooks from your SCM + # A secondary ingress will let you expose different urls + # with a different configuration + secondaryingress: + enabled: false + # paths you want forwarded to the backend + # ex /github-webhook + paths: [] + # For Kubernetes v1.14+, use 'networking.k8s.io/v1beta1' + # For Kubernetes v1.19+, use 'networking.k8s.io/v1' + apiVersion: "extensions/v1beta1" + labels: {} + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # For Kubernetes >= 1.18 you should specify the ingress-controller via the field ingressClassName + # See https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/#specifying-the-class-of-an-ingress + # ingressClassName: nginx + # configures the hostname e.g., jenkins-external.example.com + hostName: + tls: + # - secretName: jenkins-external.example.com + # hosts: + # - jenkins-external.example.com + + # If you're running on GKE and need to configure a backendconfig + # to finish ingress setup, use the following values. + # Docs: https://cloud.google.com/kubernetes-engine/docs/concepts/backendconfig + backendconfig: + # -- Enables backendconfig + enabled: false + # -- backendconfig API version + apiVersion: "extensions/v1beta1" + # -- backendconfig name + name: + # -- backendconfig labels + labels: {} + # -- backendconfig annotations + annotations: {} + # -- backendconfig spec + spec: {} + + # Openshift route + route: + # -- Enables openshift route + enabled: false + # -- Route labels + labels: {} + # -- Route annotations + annotations: {} + # -- Route path + path: + + # -- Allows for adding entries to Pod /etc/hosts + hostAliases: [] + # ref: https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + # hostAliases: + # - ip: 192.168.50.50 + # hostnames: + # - something.local + # - ip: 10.0.50.50 + # hostnames: + # - other.local + + # Expose Prometheus metrics + prometheus: + # If enabled, add the prometheus plugin to the list of plugins to install + # https://plugins.jenkins.io/prometheus + + # -- Enables prometheus service monitor + enabled: false + # -- Additional labels to add to the service monitor object + serviceMonitorAdditionalLabels: {} + # -- Set a custom namespace where to deploy ServiceMonitor resource + serviceMonitorNamespace: + # -- How often prometheus should scrape metrics + scrapeInterval: 60s + + # Defaults to the default endpoint used by the prometheus plugin + # -- The endpoint prometheus should get metrics from + scrapeEndpoint: /prometheus + + # See here: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + # The `groups` root object is added by default, add the rule entries + # -- Array of prometheus alerting rules + alertingrules: [] + # -- Additional labels to add to the PrometheusRule object + alertingRulesAdditionalLabels: {} + # -- Set a custom namespace where to deploy PrometheusRule resource + prometheusRuleNamespace: "" + + # RelabelConfigs to apply to samples before scraping. Prometheus Operator automatically adds + # relabelings for a few standard Kubernetes fields. The original scrape job’s name + # is available via the __tmp_prometheus_job_name label. + # More info: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config + relabelings: [] + # MetricRelabelConfigs to apply to samples before ingestion. + metricRelabelings: [] + + googlePodMonitor: + # If enabled, It creates Google Managed Prometheus scraping config + enabled: false + # Set a custom namespace where to deploy PodMonitoring resource + # serviceMonitorNamespace: "" + scrapeInterval: 60s + # This is the default endpoint used by the prometheus plugin + scrapeEndpoint: /prometheus + + # -- Can be used to disable rendering controller test resources when using helm template + testEnabled: true + + httpsKeyStore: + # -- Enables HTTPS keystore on jenkins controller + enable: false + # -- Name of the secret that already has ssl keystore + jenkinsHttpsJksSecretName: "" + # -- Name of the key in the secret that already has ssl keystore + jenkinsHttpsJksSecretKey: "jenkins-jks-file" + # -- Name of the secret that contains the JKS password, if it is not in the same secret as the JKS file + jenkinsHttpsJksPasswordSecretName: "" + # -- Name of the key in the secret that contains the JKS password + jenkinsHttpsJksPasswordSecretKey: "https-jks-password" + disableSecretMount: false + + # When HTTPS keystore is enabled, servicePort and targetPort will be used as HTTPS port + # -- HTTP Port that Jenkins should listen to along with HTTPS, it also serves as the liveness and readiness probes port. + httpPort: 8081 + # -- Path of HTTPS keystore file + path: "/var/jenkins_keystore" + # -- Jenkins keystore filename which will appear under controller.httpsKeyStore.path + fileName: "keystore.jks" + # -- Jenkins keystore password + password: "password" + + # -- Base64 encoded Keystore content. Keystore must be converted to base64 then being pasted here + jenkinsKeyStoreBase64Encoded: + # Convert keystore.jks files content to base64 > $ cat keystore.jks | base64 +# /u3+7QAAAAIAAAABAAAAAQANamVua2luc2NpLmNvbQAAAW2r/b1ZAAAFATCCBP0wDgYKKwYBBAEq +# AhEBAQUABIIE6QbCqasvoHS0pSwYqSvdydMCB9t+VNfwhFIiiuAelJfO5sSe2SebJbtwHgLcRz1Z +# gMtWgOSFdl3bWSzA7vrW2LED52h+jXLYSWvZzuDuh8hYO85m10ikF6QR+dTi4jra0whIFDvq3pxe +# TnESxEsN+DvbZM3jA3qsjQJSeISNpDjO099dqQvHpnCn18lyk7J4TWJ8sOQQb1EM2zDAfAOSqA/x +# QuPEFl74DlY+5DIk6EBvpmWhaMSvXzWZACGA0sYqa157dq7O0AqmuLG/EI5EkHETO4CrtBW+yLcy +# 2dUCXOMA+j+NjM1BjrQkYE5vtSfNO6lFZcISyKo5pTFlcA7ut0Fx2nZ8GhHTn32CpeWwNcZBn1gR +# pZVt6DxVVkhTAkMLhR4rL2wGIi/1WRs23ZOLGKtyDNvDHnQyDiQEoJGy9nAthA8aNHa3cfdF10vB +# Drb19vtpFHmpvKEEhpk2EBRF4fTi644Fuhu2Ied6118AlaPvEea+n6G4vBz+8RWuVCmZjLU+7h8l +# Hy3/WdUPoIL5eW7Kz+hS+sRTFzfu9C48dMkQH3a6f3wSY+mufizNF9U298r98TnYy+PfDJK0bstG +# Ph6yPWx8DGXKQBwrhWJWXI6JwZDeC5Ny+l8p1SypTmAjpIaSW3ge+KgcL6Wtt1R5hUV1ajVwVSUi +# HF/FachKqPqyLJFZTGjNrxnmNYpt8P1d5JTvJfmfr55Su/P9n7kcyWp7zMcb2Q5nlXt4tWogOHLI +# OzEWKCacbFfVHE+PpdrcvCVZMDzFogIq5EqGTOZe2poPpBVE+1y9mf5+TXBegy5HToLWvmfmJNTO +# NCDuBjgLs2tdw2yMPm4YEr57PnMX5gGTC3f2ZihXCIJDCRCdQ9sVBOjIQbOCzxFXkVITo0BAZhCi +# Yz61wt3Ud8e//zhXWCkCsSV+IZCxxPzhEFd+RFVjW0Nm9hsb2FgAhkXCjsGROgoleYgaZJWvQaAg +# UyBzMmKDPKTllBHyE3Gy1ehBNGPgEBChf17/9M+j8pcm1OmlM434ctWQ4qW7RU56//yq1soFY0Te +# fu2ei03a6m68fYuW6s7XEEK58QisJWRAvEbpwu/eyqfs7PsQ+zSgJHyk2rO95IxdMtEESb2GRuoi +# Bs+AHNdYFTAi+GBWw9dvEgqQ0Mpv0//6bBE/Fb4d7b7f56uUNnnE7mFnjGmGQN+MvC62pfwfvJTT +# EkT1iZ9kjM9FprTFWXT4UmO3XTvesGeE50sV9YPm71X4DCQwc4KE8vyuwj0s6oMNAUACW2ClU9QQ +# y0tRpaF1tzs4N42Q5zl0TzWxbCCjAtC3u6xf+c8MCGrr7DzNhm42LOQiHTa4MwX4x96q7235oiAU +# iQqSI/hyF5yLpWw4etyUvsx2/0/0wkuTU1FozbLoCWJEWcPS7QadMrRRISxHf0YobIeQyz34regl +# t1qSQ3dCU9D6AHLgX6kqllx4X0fnFq7LtfN7fA2itW26v+kAT2QFZ3qZhINGfofCja/pITC1uNAZ +# gsJaTMcQ600krj/ynoxnjT+n1gmeqThac6/Mi3YlVeRtaxI2InL82ZuD+w/dfY9OpPssQjy3xiQa +# jPuaMWXRxz/sS9syOoGVH7XBwKrWpQcpchozWJt40QV5DslJkclcr8aC2AGlzuJMTdEgz1eqV0+H +# bAXG9HRHN/0eJTn1/QAAAAEABVguNTA5AAADjzCCA4swggJzAhRGqVxH4HTLYPGO4rzHcCPeGDKn +# xTANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCY2ExEDAOBgNVBAgMB29udGFyaW8xEDAOBgNV +# BAcMB3Rvcm9udG8xFDASBgNVBAoMC2plbmtpbnN0ZXN0MRkwFwYDVQQDDBBqZW5raW5zdGVzdC5p +# bmZvMR0wGwYJKoZIhvcNAQkBFg50ZXN0QHRlc3QuaW5mbzAeFw0xOTEwMDgxNTI5NTVaFw0xOTEx +# MDcxNTI5NTVaMIGBMQswCQYDVQQGEwJjYTEQMA4GA1UECAwHb250YXJpbzEQMA4GA1UEBwwHdG9y +# b250bzEUMBIGA1UECgwLamVua2luc3Rlc3QxGTAXBgNVBAMMEGplbmtpbnN0ZXN0LmluZm8xHTAb +# BgkqhkiG9w0BCQEWDnRlc3RAdGVzdC5pbmZvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +# AQEA02q352JTHGvROMBhSHvSv+vnoOTDKSTz2aLQn0tYrIRqRo+8bfmMjXuhkwZPSnCpvUGNAJ+w +# Jrt/dqMoYUjCBkjylD/qHmnXN5EwS1cMg1Djh65gi5JJLFJ7eNcoSsr/0AJ+TweIal1jJSP3t3PF +# 9Uv21gm6xdm7HnNK66WpUUXLDTKaIs/jtagVY1bLOo9oEVeLN4nT2CYWztpMvdCyEDUzgEdDbmrP +# F5nKUPK5hrFqo1Dc5rUI4ZshL3Lpv398aMxv6n2adQvuL++URMEbXXBhxOrT6rCtYzbcR5fkwS9i +# d3Br45CoWOQro02JAepoU0MQKY5+xQ4Bq9Q7tB9BAwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAe +# 4xc+mSvKkrKBHg9/zpkWgZUiOp4ENJCi8H4tea/PCM439v6y/kfjT/okOokFvX8N5aa1OSz2Vsrl +# m8kjIc6hiA7bKzT6lb0EyjUShFFZ5jmGVP4S7/hviDvgB5yEQxOPpumkdRP513YnEGj/o9Pazi5h +# /MwpRxxazoda9r45kqQpyG+XoM4pB+Fd3JzMc4FUGxfVPxJU4jLawnJJiZ3vqiSyaB0YyUL+Er1Q +# 6NnqtR4gEBF0ZVlQmkycFvD4EC2boP943dLqNUvop+4R3SM1QMM6P5u8iTXtHd/VN4MwMyy1wtog +# hYAzODo1Jt59pcqqKJEas0C/lFJEB3frw4ImNx5fNlJYOpx+ijfQs9m39CevDq0= + +agent: + # -- Enable Kubernetes plugin jnlp-agent podTemplate + enabled: true + # -- The name of the pod template to use for providing default values + defaultsProviderTemplate: "" + + # Useful for not including a serviceAccount in the template if `false` + # -- Use `serviceAccountAgent.name` as the default value for defaults template `serviceAccount` + useDefaultServiceAccount: true + + # -- Override the default service account + # @default -- `serviceAccountAgent.name` if `agent.useDefaultServiceAccount` is `true` + serviceAccount: + + # For connecting to the Jenkins controller + # -- Overrides the Kubernetes Jenkins URL + jenkinsUrl: + + # connects to the specified host and port, instead of connecting directly to the Jenkins controller + # -- Overrides the Kubernetes Jenkins tunnel + jenkinsTunnel: + # -- Disables the verification of the controller certificate on remote connection. This flag correspond to the "Disable https certificate check" flag in kubernetes plugin UI + skipTlsVerify: false + # -- Enable the possibility to restrict the usage of this agent to specific folder. This flag correspond to the "Restrict pipeline support to authorized folders" flag in kubernetes plugin UI + usageRestricted: false + # -- The connection timeout in seconds for connections to Kubernetes API. The minimum value is 5 + kubernetesConnectTimeout: 5 + # -- The read timeout in seconds for connections to Kubernetes API. The minimum value is 15 + kubernetesReadTimeout: 15 + # -- The maximum concurrent connections to Kubernetes API + maxRequestsPerHostStr: "32" + # -- Time in minutes after which the Kubernetes cloud plugin will clean up an idle worker that has not already terminated + retentionTimeout: 5 + # -- Seconds to wait for pod to be running + waitForPodSec: 600 + # -- Namespace in which the Kubernetes agents should be launched + namespace: + # -- Custom Pod labels (an object with `label-key: label-value` pairs) + podLabels: {} + # -- Custom registry used to pull the agent jnlp image from + jnlpregistry: + image: + # -- Repository to pull the agent jnlp image from + repository: "jenkins/inbound-agent" + # -- Tag of the image to pull + tag: "3261.v9c670a_4748a_9-1" + # -- Configure working directory for default agent + workingDir: "/home/jenkins/agent" + nodeUsageMode: "NORMAL" + # -- Append Jenkins labels to the agent + customJenkinsLabels: [] + # -- Name of the secret to be used to pull the image + imagePullSecretName: + componentName: "jenkins-agent" + # -- Enables agent communication via websockets + websocket: false + directConnection: false + # -- Agent privileged container + privileged: false + # -- Configure container user + runAsUser: + # -- Configure container group + runAsGroup: + # -- Enables the agent to use the host network + hostNetworking: false + # -- Resources allocation (Requests and Limits) + resources: + requests: + cpu: "512m" + memory: "512Mi" + # ephemeralStorage: + limits: + cpu: "512m" + memory: "512Mi" + # ephemeralStorage: + livenessProbe: {} +# execArgs: "cat /tmp/healthy" +# failureThreshold: 3 +# initialDelaySeconds: 0 +# periodSeconds: 10 +# successThreshold: 1 +# timeoutSeconds: 1 + + # You may want to change this to true while testing a new image + # -- Always pull agent container image before build + alwaysPullImage: false + # When using Pod Security Admission in the Agents namespace with the restricted Pod Security Standard, + # the jnlp container cannot be scheduled without overriding its container definition with a securityContext. + # This option allows to automatically inject in the jnlp container a securityContext + # that is suitable for the use of the restricted Pod Security Standard. + # -- Set a restricted securityContext on jnlp containers + restrictedPssSecurityContext: false + # Controls how agent pods are retained after the Jenkins build completes + # Possible values: Always, Never, OnFailure + podRetention: "Never" + # Disable if you do not want the Yaml the agent pod template to show up + # in the job Console Output. This can be helpful for either security reasons + # or simply to clean up the output to make it easier to read. + showRawYaml: true + + # You can define the volumes that you want to mount for this container + # Allowed types are: ConfigMap, EmptyDir, EphemeralVolume, HostPath, Nfs, PVC, Secret + # Configure the attributes as they appear in the corresponding Java class for that type + # https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes + # -- Additional volumes + volumes: [] + # - type: ConfigMap + # configMapName: myconfigmap + # mountPath: /var/myapp/myconfigmap + # - type: EmptyDir + # mountPath: /var/myapp/myemptydir + # memory: false + # - type: EphemeralVolume + # mountPath: /var/myapp/myephemeralvolume + # accessModes: ReadWriteOnce + # requestsSize: 10Gi + # storageClassName: mystorageclass + # - type: HostPath + # hostPath: /var/lib/containers + # mountPath: /var/myapp/myhostpath + # - type: Nfs + # mountPath: /var/myapp/mynfs + # readOnly: false + # serverAddress: "192.0.2.0" + # serverPath: /var/lib/containers + # - type: PVC + # claimName: mypvc + # mountPath: /var/myapp/mypvc + # readOnly: false + # - type: Secret + # defaultMode: "600" + # mountPath: /var/myapp/mysecret + # secretName: mysecret + # Pod-wide environment, these vars are visible to any container in the agent pod + + # You can define the workspaceVolume that you want to mount for this container + # Allowed types are: DynamicPVC, EmptyDir, EphemeralVolume, HostPath, Nfs, PVC + # Configure the attributes as they appear in the corresponding Java class for that type + # https://github.com/jenkinsci/kubernetes-plugin/tree/master/src/main/java/org/csanchez/jenkins/plugins/kubernetes/volumes/workspace + # -- Workspace volume (defaults to EmptyDir) + workspaceVolume: {} + ## DynamicPVC example + # - type: DynamicPVC + # configMapName: myconfigmap + ## EmptyDir example + # - type: EmptyDir + # memory: false + ## EphemeralVolume example + # - type: EphemeralVolume + # accessModes: ReadWriteOnce + # requestsSize: 10Gi + # storageClassName: mystorageclass + ## HostPath example + # - type: HostPath + # hostPath: /var/lib/containers + ## NFS example + # - type: Nfs + # readOnly: false + # serverAddress: "192.0.2.0" + # serverPath: /var/lib/containers + ## PVC example + # - type: PVC + # claimName: mypvc + # readOnly: false + + # Pod-wide environment, these vars are visible to any container in the agent pod + # -- Environment variables for the agent Pod + envVars: [] + # - name: PATH + # value: /usr/local/bin + # -- Mount a secret as environment variable + secretEnvVars: [] + # - key: PATH + # optional: false # default: false + # secretKey: MY-K8S-PATH + # secretName: my-k8s-secret + + # -- Node labels for pod assignment + nodeSelector: {} + # Key Value selectors. Ex: + # nodeSelector + # jenkins-agent: v1 + + # -- Command to execute when side container starts + command: + # -- Arguments passed to command to execute + args: "${computer.jnlpmac} ${computer.name}" + # -- Side container name + sideContainerName: "jnlp" + + # Doesn't allocate pseudo TTY by default + # -- Allocate pseudo tty to the side container + TTYEnabled: false + # -- Max number of agents to launch + containerCap: 10 + # -- Agent Pod base name + podName: "default" + + # Enables garbage collection of orphan pods for this Kubernetes cloud. (beta) + garbageCollection: + # -- When enabled, Jenkins will periodically check for orphan pods that have not been touched for the given timeout period and delete them. + enabled: false + # -- Namespaces to look at for garbage collection, in addition to the default namespace defined for the cloud. One namespace per line. + namespaces: "" + # namespaces: |- + # namespaceOne + # namespaceTwo + # -- Timeout value for orphaned pods + timeout: 300 + + # -- Allows the Pod to remain active for reuse until the configured number of minutes has passed since the last step was executed on it + idleMinutes: 0 + + + # The raw yaml of a Pod API Object, for example, this allows usage of toleration for agent pods. + # https://github.com/jenkinsci/kubernetes-plugin#using-yaml-to-define-pod-templates + # https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + # -- The raw yaml of a Pod API Object to merge into the agent spec + yamlTemplate: "" + # yamlTemplate: |- + # apiVersion: v1 + # kind: Pod + # spec: + # tolerations: + # - key: "key" + # operator: "Equal" + # value: "value" + + # -- Defines how the raw yaml field gets merged with yaml definitions from inherited pod templates. Possible values: "merge" or "override" + yamlMergeStrategy: "override" + # -- Controls whether the defined yaml merge strategy will be inherited if another defined pod template is configured to inherit from the current one + inheritYamlMergeStrategy: false + # -- Timeout in seconds for an agent to be online + connectTimeout: 100 + # -- Annotations to apply to the pod + annotations: {} + + # Containers specified here are added to all agents. Set key empty to remove container from additional agents. + # -- Add additional containers to the agents + additionalContainers: [] + # - sideContainerName: dind + # image: + # repository: docker + # tag: dind + # command: dockerd-entrypoint.sh + # args: "" + # privileged: true + # resources: + # requests: + # cpu: 500m + # memory: 1Gi + # limits: + # cpu: 1 + # memory: 2Gi + + # Useful when configuring agents only with the podTemplates value, since the default podTemplate populated by values mentioned above will be excluded in the rendered template. + # -- Disable the default Jenkins Agent configuration + disableDefaultAgent: false + + # Below is the implementation of custom pod templates for the default configured kubernetes cloud. + # Add a key under podTemplates for each pod template. Each key (prior to | character) is just a label, and can be any value. + # Keys are only used to give the pod template a meaningful name. The only restriction is they may only contain RFC 1123 \ DNS label + # characters: lowercase letters, numbers, and hyphens. Each pod template can contain multiple containers. + # For this pod templates configuration to be loaded, the following values must be set: + # controller.JCasC.defaultConfig: true + # Best reference is https:///configuration-as-code/reference#Cloud-kubernetes. The example below creates a python pod template. + # -- Configures extra pod templates for the default kubernetes cloud + podTemplates: {} + # python: | + # - name: python + # label: jenkins-python + # serviceAccount: jenkins + # containers: + # - name: python + # image: python:3 + # command: "/bin/sh -c" + # args: "cat" + # ttyEnabled: true + # privileged: true + # resourceRequestCpu: "400m" + # resourceRequestMemory: "512Mi" + # resourceLimitCpu: "1" + # resourceLimitMemory: "1024Mi" + +# Inherits all values from `agent` so you only need to specify values which differ +# -- Configure additional +additionalAgents: {} +# maven: +# podName: maven +# customJenkinsLabels: maven +# # An example of overriding the jnlp container +# # sideContainerName: jnlp +# image: +# repository: jenkins/jnlp-agent-maven +# tag: latest +# python: +# podName: python +# customJenkinsLabels: python +# sideContainerName: python +# image: +# repository: python +# tag: "3" +# command: "/bin/sh -c" +# args: "cat" +# TTYEnabled: true + +# Here you can add additional clouds +# They inherit all values from the default cloud (including the main agent), so +# you only need to specify values which differ. If you want to override +# default additionalAgents with the additionalClouds.additionalAgents set +# additionalAgentsOverride to `true`. +additionalClouds: {} +# remote-cloud-1: +# kubernetesURL: https://api.remote-cloud.com +# additionalAgentsOverride: true +# additionalAgents: +# maven-2: +# podName: maven-2 +# customJenkinsLabels: maven +# # An example of overriding the jnlp container +# # sideContainerName: jnlp +# image: +# repository: jenkins/jnlp-agent-maven +# tag: latest +# namespace: my-other-maven-namespace +# remote-cloud-2: +# kubernetesURL: https://api.remote-cloud.com + +persistence: + # -- Enable the use of a Jenkins PVC + enabled: true + + # A manually managed Persistent Volume and Claim + # Requires persistence.enabled: true + # If defined, PVC must be created manually before volume will be bound + # -- Provide the name of a PVC + existingClaim: + + # jenkins data Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS & OpenStack) + # -- Storage class for the PVC + storageClass: + # -- Annotations for the PVC + annotations: {} + # -- Labels for the PVC + labels: {} + # -- The PVC access mode + accessMode: "ReadWriteOnce" + # -- The size of the PVC + size: "8Gi" + + # ref: https://kubernetes.io/docs/concepts/storage/volume-pvc-datasource/ + # -- Existing data source to clone PVC from + dataSource: {} + # name: PVC-NAME + # kind: PersistentVolumeClaim + + # -- SubPath for jenkins-home mount + subPath: + # -- Additional volumes + volumes: [] + # - name: nothing + # emptyDir: {} + + # -- Additional mounts + mounts: [] + # - mountPath: /var/nothing + # name: nothing + # readOnly: true + +networkPolicy: + # -- Enable the creation of NetworkPolicy resources + enabled: false + + # For Kubernetes v1.4, v1.5 and v1.6, use 'extensions/v1beta1' + # For Kubernetes v1.7, use 'networking.k8s.io/v1' + # -- NetworkPolicy ApiVersion + apiVersion: networking.k8s.io/v1 + # You can allow agents to connect from both within the cluster (from within specific/all namespaces) AND/OR from a given external IP range + internalAgents: + # -- Allow internal agents (from the same cluster) to connect to controller. Agent pods will be filtered based on PodLabels + allowed: true + # -- A map of labels (keys/values) that agent pods must have to be able to connect to controller + podLabels: {} + # -- A map of labels (keys/values) that agents namespaces must have to be able to connect to controller + namespaceLabels: {} + # project: myproject + externalAgents: + # -- The IP range from which external agents are allowed to connect to controller, i.e., 172.17.0.0/16 + ipCIDR: + # -- A list of IP sub-ranges to be excluded from the allowlisted IP range + except: [] + # - 172.17.1.0/24 + +## Install Default RBAC roles and bindings +rbac: + # -- Whether RBAC resources are created + create: true + # -- Whether the Jenkins service account should be able to read Kubernetes secrets + readSecrets: false + +serviceAccount: + # -- Configures if a ServiceAccount with this name should be created + create: true + + # The name of the ServiceAccount is autogenerated by default + # -- The name of the ServiceAccount to be used by access-controlled resources + name: + # -- Configures annotations for the ServiceAccount + annotations: {} + # -- Configures extra labels for the ServiceAccount + extraLabels: {} + # -- Controller ServiceAccount image pull secret + imagePullSecretName: + + +serviceAccountAgent: + # -- Configures if an agent ServiceAccount should be created + create: false + + # If not set and create is true, a name is generated using the fullname template + # -- The name of the agent ServiceAccount to be used by access-controlled resources + name: + # -- Configures annotations for the agent ServiceAccount + annotations: {} + # -- Configures extra labels for the agent ServiceAccount + extraLabels: {} + # -- Agent ServiceAccount image pull secret + imagePullSecretName: + +# -- Checks if any deprecated values are used +checkDeprecation: true + +awsSecurityGroupPolicies: + enabled: false + policies: + - name: "" + securityGroupIds: [] + podSelector: {} + +# Here you can configure unit tests values when executing the helm unittest in the CONTRIBUTING.md +helmtest: + # A testing framework for bash + bats: + # Bash Automated Testing System (BATS) + image: + # -- Registry of the image used to test the framework + registry: "docker.io" + # -- Repository of the image used to test the framework + repository: "bats/bats" + # -- Tag of the image to test the framework + tag: "1.11.0" diff --git a/charts/jfrog/artifactory-ha/107.90.9/.helmignore b/charts/jfrog/artifactory-ha/107.90.9/.helmignore new file mode 100644 index 0000000000..b6e97f07fb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +OWNERS + +tests/ \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/CHANGELOG.md b/charts/jfrog/artifactory-ha/107.90.9/CHANGELOG.md new file mode 100644 index 0000000000..06046425da --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/CHANGELOG.md @@ -0,0 +1,1466 @@ +# JFrog Artifactory-ha Chart Changelog +All changes to this chart will be documented in this file. + +## [107.90.9] - July 18, 2024 +* Fixed #adding colon in image registry which breaks deployment [GH-1892](https://github.com/jfrog/charts/pull/1892) +* Added new `nginx.hosts` to use Nginx server_name directive instead of `ingress.hosts` +* Added a deprecation notice of ingress.hosts when `ngnix.enabled` is true +* Added new evidence service +* Corrected database connection values based on sizing +* **IMPORTANT** +* Separate access from artifactory tomcat to run on its own dedicated tomcat + * With this change access will be running in its own dedicated container + * This will give the ability to control resources and java options specific to access + Can be done by passing the following, + `access.javaOpts.other` + `access.resources` + `access.extraEnvironmentVariables` +* Updating the example link for downloading the DB driver +* Added Binary Provider recommendations + +## [107.89.0] - May 30, 2024 +* Fix the indentation of the commented-out sections in the values.yaml file + +## [107.88.0] - May 29, 2024 +* **IMPORTANT** +* Refactored `nginx.artifactoryConf` and `nginx.mainConf` configuration (moved to files/nginx-artifactory-conf.yaml and files/nginx-main-conf.yaml instead of keys in values.yaml) + +## [107.87.0] - May 29, 2024 +* Renamed `.Values.artifactory.openMetrics` to `.Values.artifactory.metrics` +* Align all liveness and readiness probes (Removed hard-coded values) + +## [107.85.0] - May 29, 2024 +* Changed `migration.enabled` to false by default. For 6.x to 7.x migration, this flag needs to be set to `true` + +## [107.84.0] - May 29, 2024 +* Added image section for `initContainers` instead of `initContainerImage` +* Renamed `router.image.imagePullPolicy` to `router.image.pullPolicy` +* Removed loggers.image section +* Added support for `global.verisons.initContainers` to override `initContainers.image.tag` +* Fixed an issue with extraSystemYaml merge +* **IMPORTANT** +* Renamed `artifactory.setSecurityContext` to `artifactory.podSecurityContext` +* Renamed `artifactory.uid` to `artifactory.podSecurityContext.runAsUser` +* Renamed `artifactory.gid` to `artifactory.podSecurityContext.runAsGroup` and `artifactory.podSecurityContext.fsGroup` +* Renamed `artifactory.fsGroupChangePolicy` to `artifactory.podSecurityContext.fsGroupChangePolicy` +* Renamed `artifactory.seLinuxOptions` to `artifactory.podSecurityContext.seLinuxOptions` +* Added flag `allowNonPostgresql` defaults to false +* Update postgresql tag version to `15.6.0-debian-12-r5` +* Added a check if `initContainerImage` exists +* Fixed a wrong imagePullPolicy configuration +* Fixed an issue to generate unified secret to support artifactory fullname [GH-1882](https://github.com/jfrog/charts/issues/1882) +* Fixed an issue template render on loggers [GH-1883](https://github.com/jfrog/charts/issues/1883) +* Override metadata and observability image tag with `global.verisons.artifactory` value +* Fixed resource constraints for "setup" initContainer of nginx deployment [GH-962] (https://github.com/jfrog/charts/issues/962) +* Added .Values.artifactory.unifiedSecretsPrependReleaseName` for unified secret to prepend release name +* Fixed maxCacheSize and cacheProviderDir mix up under azure-blob-storage-v2-direct template in binarystore.xml + +## [107.83.0] - Mar 12, 2024 +* Added image section for `metadata` and `observability` + +## [107.82.0] - Mar 04, 2024 +* Added `disableRouterBypass` flag as experimental feature, to disable the artifactoryPath /artifactory/ and route all traffic through the Router. +* Removed Replicator Service + +## [107.81.0] - Feb 20, 2024 +* **IMPORTANT** +* Refactored systemYaml configuration (moved to files/system.yaml instead of key in values.yaml) +* Added ability to provide `extraSystemYaml` configuration in values.yaml which will merge with the existing system yaml when `systemYamlOverride` is not given [GH-1848](https://github.com/jfrog/charts/pull/1848) +* Added option to modify the new cache configs, maxFileSizeLimit and skipDuringUpload +* Added IPV4/IPV6 Dualstack flag support for Artifactory and nginx service +* Added `singleStackIPv6Cluster` flag, which manages the Nginx configuration to enable listening on IPv6 and proxying +* Fixing broken link for creating additional kubernetes resources. Refer [here](https://github.com/jfrog/log-analytics-prometheus/blob/master/helm/artifactory-ha-values.yaml) +* Refactored installerInfo configuration (moved to files/installer-info.json instead of key in values.yaml) + +## [107.80.0] - Feb 20, 2024 +* Updated README.md to create a namespace using `--create-namespace` as part of helm install + +## [107.79.0] - Feb 20, 2024 +* **IMPORTANT** +* Added `unifiedSecretInstallation` flag which enables single unified secret holding all internal (chart) secrets to `true` by default +* Added support for azure-blob-storage-v2-direct config +* Added option to set Nginx to write access_log to container STDOUT +* **Important change:** +* Update postgresql tag version to `15.2.0-debian-11-r23` +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default bundles PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x/12.x/13.x's postgresql.image.tag, previous postgresql.persistence.size and databaseUpgradeReady=true + +## [107.77.0] - April 22, 2024 +* Removed integration service +* Added recommended postgresql sizing configurations under sizing directory +* Updated artifactory-federation (probes, port, embedded mode) +* **IMPORTANT** +* setSecurityContext has been renamed to podSecurityContext. +* Moved podSecurityContext to values.yaml +* Fixing broken nginx port [GH-1860](https://github.com/jfrog/charts/issues/1860) +* Added nginx.customCommand to use custom commands for the nginx container + +## [107.76.0] - Dec 13, 2023 +* Added connectionTimeout and socketTimeout paramaters under AWSS3 binarystore section +* Reduced nginx startupProbe initialDelaySeconds + +## [107.74.0] - Nov 30, 2023 +* Added recommended sizing configurations under sizing directory, please refer [here](README.md/#apply-sizing-configurations-to-the-chart) +* **IMPORTANT** +* Added min kubeVersion ">= 1.19.0-0" in chart.yaml + +## [107.70.0] - Nov 30, 2023 +* Fixed - StatefulSet pod annotations changed from range to toYaml [GH-1828](https://github.com/jfrog/charts/issues/1828) +* Fixed - Invalid format for awsS3V3 `multiPartLimit,multipartElementSize` in binarystore.xml +* Fixed - Artifactory primary service condition +* Fixed - SecurityContext with runAsGroup in artifactory-ha [GH-1838](https://github.com/jfrog/charts/issues/1838) +* Added support for custom labels in the Nginx pods [GH-1836](https://github.com/jfrog/charts/pull/1836) +* Added podSecurityContext and containerSecurityContext for nginx +* Added support for nginx on openshift, set `podSecurityContext` and `containerSecurityContext` to false +* Renamed nginx internalPort 80,443 to 8080,8443 to support openshift + +## [107.69.0] - Sep 18, 2023 +* Adjust rtfs context +* Fixed - Metadata service does not respect customVolumeMounts for DB CAs [GH-1815](https://github.com/jfrog/charts/issues/1815) + +## [107.68.8] - Sep 18, 2023 +* Reverted - Enabled `unifiedSecretInstallation` by default [GH-1819](https://github.com/jfrog/charts/issues/1819) +* Removed unused `artifactory.javaOpts` from values.yaml +* Removed openshift condition check from NOTES.txt +* Fixed an issue with artifactory node replicaCount [GH-1808](https://github.com/jfrog/charts/issues/1808) + +## [107.68.7] - Aug 28, 2023 +* Enabled `unifiedSecretInstallation` by default +* Removed unused `artifactory.javaOpts` from values.yaml + +## [107.67.0] - Aug 28, 2023 +* Add 'extraJavaOpts' and 'port' values to federation service + +## [107.66.0] - Aug 28, 2023 +* Added federation service container in artifactory +* Add rtfs service to ingress in artifactory + +## [107.64.0] - Aug 28,2023 +* Added support to configure event.webhooks within generated system.yaml +* Fixed an issue to generate ssl certificate should support artifactory-ha fullname +* Added 'multiPartLimit' and 'multipartElementSize' parameters to awsS3V3 binary providers. +* Increased default Artifactory Tomcat acceptCount config to 400 +* Fixed Illegal Strict-Transport-Security header in nginx config + +## [107.63.0] - Aug 28, 2023 +* Added support for Openshift by adding the securityContext in container level. +* **IMPORTANT** +* Disable securityContext in container and pod level to deploy postgres on openshift. +* Fixed support for fsGroup in non openshift environment and runAsGroup in openshift environment. +* Fixed - Helm Template Error when using artifactory.loggers [GH-1791](https://github.com/jfrog/charts/issues/1791) +* Removed the nginx disable condition for openshift +* Fixed jfconnect disabling as micro-service on splitcontainers [GH-1806](https://github.com/jfrog/charts/issues/1806) + +## [107.62.0] - Jun 5, 2023 +* Added support for 'port' and 'useHttp' parameters for s3-storage-v3 binary provider [GH-1767](https://github.com/jfrog/charts/issues/1767) + +## [107.61.0] - May 31, 2023 +* Added new binary provider `google-storage-v2-direct` + +## [107.60.0] - May 31, 2023 +* Enabled `splitServicesToContainers` to true by default +* Updated the recommended values for small, medium and large installations to support the 'splitServicesToContainers' + +## [107.59.0] - May 31, 2023 +* Fixed reference of `terminationGracePeriodSeconds` +* **Breaking change** +* Updated the defaults of replicaCount (Values.artifactory.primary.replicaCount and Values.artifactory.node.replicaCount) to support Cloud-Native High Availability. Refer [Cloud-Native High Availability](https://jfrog.com/help/r/jfrog-installation-setup-documentation/cloud-native-high-availability) +* Updated the values of the recommended resources - values-small, values-medium and values-large according to the Cloud-Native HA support. +* **IMPORTANT** +* In the absence of custom parameters for primary.replicaCount and node.replicaCount on your deployment, it is recommended to specify the current values explicitly to prevent any undesired changes to the deployment structure. +* Please be advised that the configuration for resources allocation (requests, limits, javaOpts, affinity rules, etc) will now be applied solely under Values.artifactory.primary when using the new defaults. +* **Upgrade** +* Upgrade from primary-members to primary-only is recommended, and can be done by deploy the chart with the new values. +* During the upgrade, members pods should be deleted and new primary pods should be created. This might trigger the creation of new PVCs. +* Added Support for Cold Artifact Storage as part of the systemYaml configuration (disabled by default) +* Added new binary provider `s3-storage-v3-archive` +* Fixed jfconnect disabling as micro-service on non-splitcontainers +* Fixed an issue whereby, Artifactory failed to start when using persistence storage type `nfs` due to missing binarystore.xml + + +## [107.58.0] - Mar 23, 2023 +* Updated postgresql multi-arch tag version to `13.10.0-debian-11-r14` +* Removed obselete remove-lost-found initContainer` +* Added env JF_SHARED_NODE_HAENABLED under frontend when running in the container split mode + +## [107.57.0] - Mar 02, 2023 +* Updated initContainerImage and logger image to `ubi9/ubi-minimal:9.1.0.1793` + +## [107.55.0] - Feb 21, 2023 +* Updated initContainerImage and logger image to `ubi9/ubi-minimal:9.1.0.1760` +* Adding a custom preStop to Artifactory router for allowing graceful termination to complete +* Fixed an invalid reference of node selector on artifactory-ha chart + +## [107.53.0] - Jan 20, 2023 +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.7.1049` + +## [107.50.0] - Jan 20, 2023 +* Updated postgresql tag version to `13.9.0-debian-11-r11` +* Fixed make lint issue on artifactory-ha chart [GH-1714](https://github.com/jfrog/charts/issues/1714) +* Fixed an issue for capabilities check of ingress +* Updated jfrogUrl text path in migrate.sh file +* Added a note that from 107.46.x chart versions, `copyOnEveryStartup` is not needed for binarystore.xml, it is always copied via initContainers. For more Info, Refer [GH-1723](https://github.com/jfrog/charts/issues/1723) + +## [107.49.0] - Jan 16, 2023 +* Changed logic in wait-for-primary container to use /dev/tcp instead of curl +* Added support for setting `seLinuxOptions` in `securityContext` [GH-1700](https://github.com/jfrog/charts/pull/1700) +* Added option to enable/disable proxy_request_buffering and proxy_buffering_off [GH-1686](https://github.com/jfrog/charts/pull/1686) +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.7.1049` + +## [107.48.0] - Oct 27, 2022 +* Updated router version to `7.51.0` + +## [107.47.0] - Sep 29, 2022 +* Updated initContainerImage to `ubi8/ubi-minimal:8.6-941` +* Added support for annotations for artifactory statefulset and nginx deployment [GH-1665](https://github.com/jfrog/charts/pull/1665) +* Updated router version to `7.49.0` + +## [107.46.0] - Sep 14, 2022 +* **IMPORTANT** +* Added support for lifecycle hooks for all containers, changed `artifactory.postStartCommand` to `.Values.artifactory.lifecycle.postStart.exec.command` +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.6-902` +* Update nginx configuration to allow websocket requests when using pipelines +* Fixed an issue to allow artifactory to make direct API calls to store instead via jfconnect service when `splitServicesToContainers=true` +* Refactor binarystore.xml configuration (moved to `files/binarystore.xml` instead of key in values.yaml) +* Added new binary providers `s3-storage-v3-direct`, `azure-blob-storage-direct`, `google-storage-v2` +* Deprecated (removed) `aws-s3` binary provider [JetS3t library](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Filestore#ConfiguringtheFilestore-BinaryProvider) +* Deprecated (removed) `google-storage` binary provider and force persistence storage type `google-storage` to work with `google-storage-v2` only +* Copy binarystore.xml in init Container to fix existing persistence on file system in clear text +* Removed obselete `.Values.artifactory.binarystore.enabled` key +* Removed `newProbes.enabled`, default to new probes +* Added nginx.customCommand using inotifyd to reload nginx's config upon ssl secret or configmap changes [GH-1640](https://github.com/jfrog/charts/pull/1640) + +## [107.43.0] - Aug 25, 2022 +* Added flag `artifactory.replicator.ingress.enabled` to enable/disable ingress for replicator +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.6-854` +* Updated router version to `7.45.0` +* Added flag `artifactory.schedulerName` to set for the pods the value of schedulerName field [GH-1606](https://github.com/jfrog/charts/issues/1606) +* Enabled TLS based on access or router in values.yaml + +## [107.42.0] - Aug 25, 2022 +* Enabled database creds secret to use from unified secret +* Updated router version to `7.42.0` +* Added support to truncate (> 63 chars) for unifiedCustomSecretVolumeName + +## [107.41.0] - June 27, 2022 +* Added support for nginx.terminationGracePeriodSeconds [GH-1645](https://github.com/jfrog/charts/issues/1645) +* Fix nginx lifecycle values [GH-1646](https://github.com/jfrog/charts/pull/1646) +* Use an alternate command for `find` to copy custom certificates +* Added support for circle of trust using `circleOfTrustCertificatesSecret` secret name [GH-1623](https://github.com/jfrog/charts/pull/1623) + +## [107.40.0] - Jun 16, 2022 +* Deprecated k8s PodDisruptionBudget api policy/v1beta1 [GH-1618](https://github.com/jfrog/charts/issues/1618) +* Disabled node PodDisruptionBudget, statefulset and artifactory-primary service from artifactory-ha chart when member nodes are 0 +* From artifactory 7.38.x, joinKey can be retrived from Admin > User Management > Settings in UI +* Fixed template name for artifactory-ha database creds [GH-1602](https://github.com/jfrog/charts/pull/1602) +* Allow templating for pod annotations [GH-1634](https://github.com/jfrog/charts/pull/1634) +* Added flags to control enable/disable infra services in splitServicesToContainers + +## [107.39.0] - May 16, 2022 +* Fix default `artifactory.async.corePoolSize` [GH-1612](https://github.com/jfrog/charts/issues/1612) +* Added support of nginx annotations +* Reduce startupProbe `initialDelaySeconds` +* Align all liveness and readiness probes failureThreshold to `5` seconds +* Added new flag `unifiedSecretInstallation` to enables single unified secret holding all the artifactory-ha secrets +* Updated router version to `7.38.0` + +## [107.38.0] - May 04, 2022 +* Added support for `global.nodeSelector` to artifactory and nginx pods +* Updated router version to `7.36.1` +* Added support for custom global probes timeout +* Updated frontend container command +* Added topologySpreadConstraints to artifactory and nginx, and add lifecycle hooks to nginx [GH-1596](https://github.com/jfrog/charts/pull/1596) +* Added support of extraEnvironmentVariables for all infra services containers +* Enabled the consumption (jfconnect) flag by default +* Fix jfconnect disabling on non-splitcontainers + +## [107.37.0] - Mar 08, 2022 +* Added support for customPorts in nginx deployment +* Bugfix - Wrong proxy_pass configurations for /artifactory/ in the default artifactory.conf +* Added signedUrlExpirySeconds option to artifactory.persistence.type aws-S3-V3 +* Updated router version to `7.35.0` +* Added useInstanceCredentials,enableSignedUrlRedirect option to google-storage-v2 +* Changed dependency charts repo to `charts.jfrog.io` + +## [107.36.0] - Mar 03, 2022 +* Remove pdn tracker which starts replicator service +* Added silent option for curl probes +* Added readiness health check for the artifactory container for k8s version < 1.20 +* Fix property file migration issue to system.yaml 6.x to 7.x + +## [107.35.0] - Feb 08, 2022 +* Updated router version to `7.32.1` + +## [107.33.0] - Jan 11, 2022 +* Make default value of anti-affinity to soft +* Readme fixes +* Added support for setting `fsGroupChangePolicy` +* Added nginx customInitContainers, customVolumes, customSidecarContainers [GH-1565](https://github.com/jfrog/charts/pull/1565) +* Updated router version to `7.30.0` + +## [107.32.0] - Dec 23, 2021 +* Updated logger image to `jfrog/ubi-minimal:8.5-204` +* Added default `8091` as `artifactory.tomcat.maintenanceConnector.port` for probes check +* Refactored probes to replace httpGet probes with basic exec + curl +* Refactored `database-creds` secret to create only when database values are passed +* Added new endpoints for probes `/artifactory/api/v1/system/liveness` and `/artifactory/api/v1/system/readiness` +* Enabled `newProbes:true` by default to use these endpoints +* Fix filebeat sidecar spool file permissions +* Updated filebeat sidecar container to `7.16.2` + +## [107.31.0] - Dec 17, 2021 +* Remove integration service feature flag to make it mandatory service +* Update postgresql tag version to `13.4.0-debian-10-r39` +* Refactored `router.requiredServiceTypes` to support platform chart + +## [107.30.0] - Nov 30, 2021 +* Fixed incorrect permission for filebeat.yaml +* Updated healthcheck (liveness/readiness) api for integration service +* Disable readiness health check for the artifactory container when running in the container split mode +* Ability to start replicator on enabling pdn tracker + +## [107.29.0] - Nov 30, 2021 +* Added integration service container in artifactory +* Add support for Ingress Class Name in Ingress Spec [GH-1516](https://github.com/jfrog/charts/pull/1516) +* Fixed chart values to use curl instead of wget [GH-1529](https://github.com/jfrog/charts/issues/1529) +* Updated nginx config to allow websockets when pipelines is enabled +* Moved router.topology.local.requireqservicetypes from system.yaml to router as environment variable +* Added jfconnect in system.yaml +* Updated artifactory container’s health probes to use artifactory api on rt-split +* Updated initContainerImage to `jfrog/ubi-minimal:8.5-204` +* Updated router version to `7.28.2` +* Set Jfconnect enabled to `false` in the artifactory container when running in the container split mode + +## [107.28.0] - Nov 11, 2021 +* Added default values cpu and memeory in initContainers +* Updated router version to `7.26.0` +* Bug fix - jmx port not exposed in artifactory service +* Updated (`rbac.create` and `serviceAccount.create` to false by default) for least privileges +* Fixed incorrect data type for `Values.router.serviceRegistry.insecure` in default values.yaml [GH-1514](https://github.com/jfrog/charts/pull/1514/files) +* **IMPORTANT** +* Changed init-container images from `alpine` to `ubi8/ubi-minimal` +* Added support for AWS License Manager using `.Values.aws.licenseConfigSecretName` + +## [107.27.0] - Oct 6, 2021 +* **Breaking change** +* Aligned probe structure (moved probes variables under config block) +* Added support for new probes(set to false by default) +* Bugfix - Invalid format for `multiPartLimit,multipartElementSize,maxCacheSize` in binarystore.xml [GH-1466](https://github.com/jfrog/charts/issues/1466) +* Added missioncontrol container in artifactory +* Dropped NET_RAW capability for the containers +* Added resources to migration-artifactory init container +* Added resources to all rt split containers +* Updated router version to `7.25.1` +* Added support for Ingress networking.k8s.io/v1/Ingress for k8s >=1.22 [GH-1487](https://github.com/jfrog/charts/pull/1487) +* Added min kubeVersion ">= 1.14.0-0" in chart.yaml +* Update alpine tag version to `3.14.2` +* Update busybox tag version to `1.33.1` +* Update postgresql tag version to `13.4.0-debian-10-r39` + +## [107.26.0] - Aug 20, 2021 +* Added Observability container (only when `splitServicesToContainers` is enabled) +* Added min kubeVersion ">= 1.12.0-0" in chart.yaml + +## [107.25.0] - Aug 13, 2021 +* Updated readme of chart to point to wiki. Refer [Installing Artifactory](https://www.jfrog.com/confluence/display/JFROG/Installing+Artifactory) +* Added startupProbe and livenessProbe for RT-split containers +* Updated router version to 7.24.1 +* Added security hardening fixes +* Enabled startup probes for k8s >= 1.20.x +* Changed network policy to allow all ingress and egress traffic +* Added Observability changes +* Added support for global.versions.router (only when `splitServicesToContainers` is enabled) + +## [107.24.0] - July 27, 2021 +* Support global and product specific tags at the same time +* Added support for artifactory containers split + +## [107.23.0] - July 8, 2021 +* Bug fix - logger sideCar picks up Wrong File in helm +* Allow filebeat metrics configuration in values.yaml + +## [107.22.0] - July 6, 2021 +* Update alpine tag version to `3.14.0` +* Added `nodePort` support to artifactory-service and nginx-service templates +* Removed redundant `terminationGracePeriodSeconds` in statefulset +* Increased `startupProbe.failureThreshold` time + +## [107.21.3] - July 2, 2021 +* Added ability to change sendreasonphrase value in server.xml via system yaml + +## [107.19.3] - May 20, 2021 +* Fix broken support for startupProbe for k8s < 1.18.x +* Removed an extraneous resources block from the prepare-custom-persistent-volume container in the primary statefulset +* Added support for `nameOverride` and `fullnameOverride` in values.yaml + +## [107.18.6] - May 4, 2021 +* Removed `JF_SHARED_NODE_PRIMARY` env to support for Cloud Native HA +* Bumping chart version to align with app version +* Add `securityContext` option on nginx container + +## [5.0.0] - April 22, 2021 +* **Breaking change:** +* Increased default postgresql persistence size to `200Gi` +* Update postgresql tag version to `13.2.0-debian-10-r55` +* Update postgresql chart version to `10.3.18` in chart.yaml - [10.x Upgrade Notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1000) +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x/12.x's postgresql.image.tag, previous postgresql.persistence.size and databaseUpgradeReady=true +* **IMPORTANT** +* This chart is only helm v3 compatible +* Fix support for Cloud Native HA +* Fixed filebeat-configmap naming +* Explicitly set ServiceAccount `automountServiceAccountToken` to 'true' +* Update alpine tag version to `3.13.5` + +## [4.13.2] - April 15, 2021 +* Updated Artifactory version to 7.17.9 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.9) + +## [4.13.1] - April 6, 2021 +* Updated Artifactory version to 7.17.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.6) +* Update alpine tag version to `3.13.4` + +## [4.13.0] - April 5, 2021 +* **IMPORTANT** +* Added `charts.jfrog.io` as default JFrog Helm repository +* Updated Artifactory version to 7.17.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.5) + +## [4.12.2] - Mar 31, 2021 +* Updated Artifactory version to 7.17.4 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.4) + +## [4.12.1] - Mar 30, 2021 +* Updated Artifactory version to 7.17.3 +* Add `timeoutSeconds` to all exec probes - Please refer [here](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes) + +## [4.12.0] - Mar 24, 2021 +* Updated Artifactory version to 7.17.2 +* Optimized startupProbe time + +## [4.11.0] - Mar 18, 2021 +* Add support to startupProbe + +## [4.10.0] - Mar 15, 2021 +* Updated Artifactory version to 7.16.3 + +## [4.9.5] - Mar 09, 2021 +* Added HSTS header to nginx conf + +## [4.9.4] - Mar 9, 2021 +* Removed bintray URL references in the chart + +## [4.9.3] - Mar 04, 2021 +* Updated Artifactory version to 7.15.4 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.15.4) + +## [4.9.2] - Mar 04, 2021 +* Fixed creation of nginx-certificate-secret when Nginx is disabled + +## [4.9.1] - Feb 19, 2021 +* Update busybox tag version to `1.32.1` + +## [4.9.0] - Feb 18, 2021 +* Updated Artifactory version to 7.15.3 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.15.3) +* Add option to specify update strategy for Artifactory statefulset + +## [4.8.1] - Feb 11, 2021 +* Exposed "multiPartLimit" and "multipartElementSize" for the Azure Blob Storage Binary Provider + +## [4.8.0] - Feb 08, 2021 +* Updated Artifactory version to 7.12.8 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.12.8) +* Support for custom certificates using secrets +* **Important:** Switched docker images download from `docker.bintray.io` to `releases-docker.jfrog.io` +* Update alpine tag version to `3.13.1` + +## [4.7.9] - Feb 3, 2021 +* Fix copyOnEveryStartup for HA cluster license + +## [4.7.8] - Jan 25, 2021 +* Add support for hostAliases + +## [4.7.7] - Jan 11, 2021 +* Fix failures when using creds file for configurating google storage + +## [4.7.6] - Jan 11, 2021 +* Updated Artifactory version to 7.12.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.12.6) + +## [4.7.5] - Jan 07, 2021 +* Added support for optional tracker dedicated ingress `.Values.artifactory.replicator.trackerIngress.enabled` (defaults to false) + +## [4.7.4] - Jan 04, 2021 +* Fixed gid support for statefulset + +## [4.7.3] - Dec 31, 2020 +* Added gid support for statefulset +* Add setSecurityContext flag to allow securityContext block to be removed from artifactory statefulset + +## [4.7.2] - Dec 29, 2020 +* **Important:** Removed `.Values.metrics` and `.Values.fluentd` (Fluentd and Prometheus integrations) +* Add support for creating additional kubernetes resources - [refer here](https://github.com/jfrog/log-analytics-prometheus/blob/master/artifactory-ha-values.yaml) +* Updated Artifactory version to 7.12.5 + +## [4.7.1] - Dec 21, 2020 +* Updated Artifactory version to 7.12.3 + +## [4.7.0] - Dec 18, 2020 +* Updated Artifactory version to 7.12.2 +* Added `.Values.artifactory.openMetrics.enabled` + +## [4.6.1] - Dec 11, 2020 +* Added configurable `.Values.global.versions.artifactory` in values.yaml + +## [4.6.0] - Dec 10, 2020 +* Update postgresql tag version to `12.5.0-debian-10-r25` +* Fixed `artifactory.persistence.googleStorage.endpoint` from `storage.googleapis.com` to `commondatastorage.googleapis.com` +* Updated chart maintainers email + +## [4.5.5] - Dec 4, 2020 +* **Important:** Renamed `.Values.systemYaml` to `.Values.systemYamlOverride` + +## [4.5.4] - Dec 1, 2020 +* Improve error message returned when attempting helm upgrade command + +## [4.5.3] - Nov 30, 2020 +* Updated Artifactory version to 7.11.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.11) + +# [4.5.2] - Nov 23, 2020 +* Updated Artifactory version to 7.11.2 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.11) +* Updated port namings on services and pods to allow for istio protocol discovery +* Change semverCompare checks to support hosted Kubernetes +* Add flag to disable creation of ServiceMonitor when enabling prometheus metrics +* Prevent the PostHook command to be executed if the user did not specify a command in the values file +* Fix issue with tls file generation when nginx.https.enabled is false + +## [4.5.1] - Nov 19, 2020 +* Updated Artifactory version to 7.11.2 +* Bugfix - access.config.import.xml override Access Federation configurations + +## [4.5.0] - Nov 17, 2020 +* Updated Artifactory version to 7.11.1 +* Update alpine tag version to `3.12.1` + +## [4.4.6] - Nov 10, 2020 +* Pass system.yaml via external secret for advanced usecases +* Added support for custom ingress +* Bugfix - stateful set not picking up changes to database secrets + +## [4.4.5] - Nov 9, 2020 +* Updated Artifactory version to 7.10.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.6) + +## [4.4.4] - Nov 2, 2020 +* Add enablePathStyleAccess property for aws-s3-v3 binary provider template + +## [4.4.3] - Nov 2, 2020 +* Updated Artifactory version to 7.10.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.5) + +## [4.4.2] - Oct 22, 2020 +* Chown bug fix where Linux capability cannot chown all files causing log line warnings +* Fix Frontend timeout linting issue + +## [4.4.1] - Oct 20, 2020 +* Add flag to disable prepare-custom-persistent-volume init container + +## [4.4.0] - Oct 19, 2020 +* Updated Artifactory version to 7.10.2 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.2) + +## [4.3.4] - Oct 19, 2020 +* Add support to specify priorityClassName for nginx deployment + +## [4.3.3] - Oct 15, 2020 +* Fixed issue with node PodDisruptionBudget which also getting applied on the primary +* Fix mandatory masterKey check issue when upgrading from 6.x to 7.x + +## [4.3.2] - Oct 14, 2020 +* Add support to allow more than 1 Primary in Artifactory-ha STS + +## [4.3.1] - Oct 9, 2020 +* Add global support for customInitContainersBegin + +## [4.3.0] - Oct 07, 2020 +* Updated Artifactory version to 7.9.1 +* **Breaking change:** Fix `storageClass` to correct `storageClassName` in values.yaml + +## [4.2.0] - Oct 5, 2020 +* Expose Prometheus metrics via a ServiceMonitor +* Parse log files for metric data with Fluentd + +## [4.1.0] - Sep 30, 2020 +* Updated Artifactory version to 7.9.0 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.9) + +## [4.0.12] - Sep 25, 2020 +* Update to use linux capability CAP_CHOWN instead of root base init container to avoid any use of root containers to pass Redhat security requirements + +## [4.0.11] - Sep 28, 2020 +* Setting chart coordinates in migitation yaml + +## [4.0.10] - Sep 25, 2020 +* Update filebeat version to `7.9.2` + +## [4.0.9] - Sep 24, 2020 +* Fixed broken issue - when setting `waitForDatabase:false` container startup still waits for DB + +## [4.0.8] - Sep 22, 2020 +* Updated readme + +## [4.0.7] - Sep 22, 2020 +* Fix lint issue in migitation yaml + +## [4.0.6] - Sep 22, 2020 +* Fix broken migitation yaml + +## [4.0.5] - Sep 21, 2020 +* Added mitigation yaml for Artifactory - [More info](https://github.com/jfrog/chartcenter/blob/master/docs/securitymitigationspec.md) + +## [4.0.4] - Sep 17, 2020 +* Added configurable session(UI) timeout in frontend microservice + +## [4.0.3] - Sep 17, 2020 +* Fix small typo in README and added proper required text to be shown while postgres upgrades + +## [4.0.2] - Sep 14, 2020 +* Updated Artifactory version to 7.7.8 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.7.8) + +## [4.0.1] - Sep 8, 2020 +* Added support for artifactory pro license (single node) installation. + +## [4.0.0] - Sep 2, 2020 +* **Breaking change:** Changed `imagePullSecrets` value from string to list +* **Breaking change:** Added `image.registry` and changed `image.version` to `image.tag` for docker images +* Added support for global values +* Updated maintainers in chart.yaml +* Update postgresql tag version to `12.3.0-debian-10-r71` +* Update postgresqlsub chart version to `9.3.4` - [9.x Upgrade Notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#900) +* **IMPORTANT** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x's postgresql.image.tag and databaseUpgradeReady=true. + +## [3.1.0] - Aug 13, 2020 +* Updated Artifactory version to 7.7.3 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.7) + +## [3.0.15] - Aug 10, 2020 +* Added enableSignedUrlRedirect for persistent storage type aws-s3-v3. + +## [3.0.14] - Jul 31, 2020 +* Update the README section on Nginx SSL termination to reflect the actual YAML structure. + +## [3.0.13] - Jul 30, 2020 +* Added condition to disable the migration scripts. + +## [3.0.12] - Jul 29, 2020 +* Document Artifactory node affinity. + +## [3.0.11] - Jul 28, 2020 +* Added maxConnections for persistent storage type aws-s3-v3. + +## [3.0.10] - Jul 28, 2020 +Bugfix / support for userPluginSecrets with Artifactory 7 + +## [3.0.9] - Jul 27, 2020 +* Add tpl to external database secrets. +* Modified `scheme` to `artifactory-ha.scheme` + +## [3.0.8] - Jul 23, 2020 +* Added condition to disable the migration init container. + +## [3.0.7] - Jul 21, 2020 +* Updated Artifactory-ha Chart to add node and primary labels to pods and service objects. + +## [3.0.6] - Jul 20, 2020 +* Support custom CA and certificates + +## [3.0.5] - Jul 13, 2020 +* Updated Artifactory version to 7.6.3 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.6.3 +* Fixed Mysql database jar path in `preStartCommand` in README + +## [3.0.4] - Jul 8, 2020 +* Move some postgresql values to where they should be according to the subchart + +## [3.0.3] - Jul 8, 2020 +* Set Artifactory access client connections to the same value as the access threads. + +## [3.0.2] - Jul 6, 2020 +* Updated Artifactory version to 7.6.2 +* **IMPORTANT** +* Added ChartCenter Helm repository in README + +## [3.0.1] - Jul 01, 2020 +* Add dedicated ingress object for Replicator service when enabled + +## [3.0.0] - Jun 30, 2020 +* Update postgresql tag version to `10.13.0-debian-10-r38` +* Update alpine tag version to `3.12` +* Update busybox tag version to `1.31.1` +* **IMPORTANT** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass postgresql.image.tag=9.6.18-debian-10-r7 and databaseUpgradeReady=true + +## [2.6.0] - Jun 29, 2020 +* Updated Artifactory version to 7.6.1 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.6.1 +* Add tpl for external database secrets + +## [2.5.8] - Jun 25, 2020 +* Stop loading the Nginx stream module because it is now a core module + +## [2.5.7] - Jun 18, 2020 +* Fixes bootstrap configMap issue on member node + +## [2.5.6] - Jun 11, 2020 +* Support list of custom secrets + +## [2.5.5] - Jun 11, 2020 +* NOTES.txt fixed incorrect information + +## [2.5.4] - Jun 12, 2020 +* Updated Artifactory version to 7.5.7 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.5.7 + +## [2.5.3] - Jun 8, 2020 +* Statically setting primary service type to ClusterIP. +* Prevents primary service from being exposed publicly when using LoadBalancer type on cloud providers. + +## [2.5.2] - Jun 8, 2020 +* Readme update - configuring Artifactory with oracledb + +## [2.5.1] - Jun 5, 2020 +* Fixes broken PDB issue upgrading from 6.x to 7.x + +## [2.5.0] - Jun 1, 2020 +* Updated Artifactory version to 7.5.5 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.5 +* Fixes bootstrap configMap permission issue +* Update postgresql tag version to `9.6.18-debian-10-r7` + +## [2.4.10] - May 27, 2020 +* Added Tomcat maxThreads & acceptCount + +## [2.4.9] - May 25, 2020 +* Fixed postgresql README `image` Parameters + +## [2.4.8] - May 24, 2020 +* Fixed typo in README regarding migration timeout + +## [2.4.7] - May 19, 2020 +* Added metadata maxOpenConnections + +## [2.4.6] - May 07, 2020 +* Fix `installerInfo` string format + +## [2.4.5] - Apr 27, 2020 +* Updated Artifactory version to 7.4.3 + +## [2.4.4] - Apr 27, 2020 +* Change customInitContainers order to run before the "migration-ha-artifactory" initContainer + +## [2.4.3] - Apr 24, 2020 +* Fix `artifactory.persistence.awsS3V3.useInstanceCredentials` incorrect conditional logic +* Bump postgresql tag version to `9.6.17-debian-10-r72` in values.yaml + +## [2.4.2] - Apr 16, 2020 +* Custom volume mounts in migration init container. + +## [2.4.1] - Apr 16, 2020 +* Fix broken support for gcpServiceAccount for googleStorage + +## [2.4.0] - Apr 14, 2020 +* Updated Artifactory version to 7.4.1 + +## [2.3.1] - April 13, 2020 +* Update README with helm v3 commands + +## [2.3.0] - April 10, 2020 +* Use dependency charts from `https://charts.bitnami.com/bitnami` +* Bump postgresql chart version to `8.7.3` in requirements.yaml +* Bump postgresql tag version to `9.6.17-debian-10-r21` in values.yaml + +## [2.2.11] - Apr 8, 2020 +* Added recommended ingress annotation to avoid 413 errors + +## [2.2.10] - Apr 8, 2020 +* Moved migration scripts under `files` directory +* Support preStartCommand in migration Init container as `artifactory.migration.preStartCommand` + +## [2.2.9] - Apr 01, 2020 +* Support masterKey and joinKey as secrets + +## [2.2.8] - Apr 01, 2020 +* Ensure that the join key is also copied when provided by an external secret +* Migration container in primary and node statefulset now respects custom versions and the specified node/primary resources + +## [2.2.7] - Apr 01, 2020 +* Added cache-layer in chain definition of Google Cloud Storage template +* Fix readme use to `-hex 32` instead of `-hex 16` + +## [2.2.6] - Mar 31, 2020 +* Change the way the artifactory `command:` is set so it will properly pass a SIGTERM to java + +## [2.2.5] - Mar 31, 2020 +* Removed duplicate `artifactory-license` volume from primary node + +## [2.2.4] - Mar 31, 2020 +* Restore `artifactory-license` volume for the primary node + +## [2.2.3] - Mar 29, 2020 +* Add Nginx log options: stderr as logfile and log level + +## [2.2.2] - Mar 30, 2020 +* Apply initContainers.resources to `copy-system-yaml`, `prepare-custom-persistent-volume`, and `migration-artifactory-ha` containers +* Use the same defaulting mechanism used for the artifactory version used elsewhere in the chart +* Removed duplicate `artifactory-license` volume that prevented using an external secret + +## [2.2.1] - Mar 29, 2020 +* Fix loggers sidecars configurations to support new file system layout and new log names + +## [2.2.0] - Mar 29, 2020 +* Fix broken admin user bootstrap configuration +* **Breaking change:** renamed `artifactory.accessAdmin` to `artifactory.admin` + +## [2.1.3] - Mar 24, 2020 +* Use `postgresqlExtendedConf` for setting custom PostgreSQL configuration (instead of `postgresqlConfiguration`) + +## [2.1.2] - Mar 21, 2020 +* Support for SSL offload in Nginx service(LoadBalancer) layer. Introduced `nginx.service.ssloffload` field with boolean type. + +## [2.1.1] - Mar 23, 2020 +* Moved installer info to values.yaml so it is fully customizable + +## [2.1.0] - Mar 23, 2020 +* Updated Artifactory version to 7.3.2 + +## [2.0.36] - Mar 20, 2020 +* Add support GCP credentials.json authentication + +## [2.0.35] - Mar 20, 2020 +* Add support for masterKey trim during 6.x to 7.x migration if 6.x masterKey is 32 hex (64 characters) + +## [2.0.34] - Mar 19, 2020 +* Add support for NFS directories `haBackupDir` and `haDataDir` + +## [2.0.33] - Mar 18, 2020 +* Increased Nginx proxy_buffers size + +## [2.0.32] - Mar 17, 2020 +* Changed all single quotes to double quotes in values files +* useInstanceCredentials variable was declared in S3 settings but not used in chart. Now it is being used. + +## [2.0.31] - Mar 17, 2020 +* Fix rendering of Service Account annotations + +## [2.0.30] - Mar 16, 2020 +* Add Unsupported message from 6.18 to 7.2.x (migration) + +## [2.0.29] - Mar 11, 2020 +* Upgrade Docs update + +## [2.0.28] - Mar 11, 2020 +* Unified charts public release + +## [2.0.27] - Mar 8, 2020 +* Add an optional wait for primary node to be ready with a proper test for http status + +## [2.0.23] - Mar 6, 2020 +* Fix path to `/artifactory_bootstrap` +* Add support for controlling the name of the ingress and allow to set more than one cname + +## [2.0.22] - Mar 4, 2020 +* Add support for disabling `consoleLog` in `system.yaml` file + +## [2.0.21] - Feb 28, 2020 +* Add support to process `valueFrom` for extraEnvironmentVariables + +## [2.0.20] - Feb 26, 2020 +* Store join key to secret + +## [2.0.19] - Feb 26, 2020 +* Updated Artifactory version to 7.2.1 + +## [2.0.12] - Feb 07, 2020 +* Remove protection flag `databaseUpgradeReady` which was added to check internal postgres upgrade + +## [2.0.0] - Feb 07, 2020 +* Updated Artifactory version to 7.0.0 + +## [1.4.10] - Feb 13, 2020 +* Add support for SSH authentication to Artifactory + +## [1.4.9] - Feb 10, 2020 +* Fix custom DB password indention + +## [1.4.8] - Feb 9, 2020 +* Add support for `tpl` in the `postStartCommand` + +## [1.4.7] - Feb 4, 2020 +* Support customisable Nginx kind + +## [1.4.6] - Feb 2, 2020 +* Add a comment stating that it is recommended to use an external PostgreSQL with a static password for production installations + +## [1.4.5] - Feb 2, 2020 +* Add support for primary or member node specific preStartCommand + +## [1.4.4] - Jan 30, 2020 +* Add the option to configure resources for the logger containers + +## [1.4.3] - Jan 26, 2020 +* Improve `database.user` and `database.password` logic in order to support more use cases and make the configuration less repetitive + +## [1.4.2] - Jan 22, 2020 +* Refined pod disruption budgets to separate nginx and Artifactory pods + +## [1.4.1] - Jan 19, 2020 +* Fix replicator port config in nginx replicator configmap + +## [1.4.0] - Jan 19, 2020 +* Updated Artifactory version to 6.17.0 + +## [1.3.8] - Jan 16, 2020 +* Added example for external nginx-ingress + +## [1.3.7] - Jan 07, 2020 +* Add support for customizable `mountOptions` of NFS PVs + +## [1.3.6] - Dec 30, 2019 +* Fix for nginx probes failing when launched with http disabled + +## [1.3.5] - Dec 24, 2019 +* Better support for custom `artifactory.internalPort` + +## [1.3.4] - Dec 23, 2019 +* Mark empty map values with `{}` + +## [1.3.3] - Dec 16, 2019 +* Another fix for toggling nginx service ports + +## [1.3.2] - Dec 12, 2019 +* Fix for toggling nginx service ports + +## [1.3.1] - Dec 10, 2019 +* Add support for toggling nginx service ports + +## [1.3.0] - Dec 1, 2019 +* Updated Artifactory version to 6.16.0 + +## [1.2.4] - Nov 28, 2019 +* Add support for using existing PriorityClass + +## [1.2.3] - Nov 27, 2019 +* Add support for PriorityClass + +## [1.2.2] - Nov 20, 2019 +* Update Artifactory logo + +## [1.2.1] - Nov 18, 2019 +* Add the option to provide service account annotations (in order to support stuff like https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html) + +## [1.2.0] - Nov 18, 2019 +* Updated Artifactory version to 6.15.0 + +## [1.1.12] - Nov 17, 2019 +* Fix `README.md` format (broken table) + +## [1.1.11] - Nov 17, 2019 +* Update comment on Artifactory master key + +## [1.1.10] - Nov 17, 2019 +* Fix creation of double slash in nginx artifactory configuration + +## [1.1.9] - Nov 14, 2019 +* Set explicit `postgresql.postgresqlPassword=""` to avoid helm v3 error + +## [1.1.8] - Nov 12, 2019 +* Updated Artifactory version to 6.14.1 + +## [1.1.7] - Nov 11, 2019 +* Additional documentation for masterKey + +## [1.1.6] - Nov 10, 2019 +* Update PostgreSQL chart version to 7.0.1 +* Use formal PostgreSQL configuration format + +## [1.1.5] - Nov 8, 2019 +* Add support `artifactory.service.loadBalancerSourceRanges` for whitelisting when setting `artifactory.service.type=LoadBalancer` + +## [1.1.4] - Nov 6, 2019 +* Add support for any type of environment variable by using `extraEnvironmentVariables` as-is + +## [1.1.3] - Nov 6, 2019 +* Add nodeselector support for Postgresql + +## [1.1.2] - Nov 5, 2019 +* Add support for the aws-s3-v3 filestore, which adds support for pod IAM roles + +## [1.1.1] - Nov 4, 2019 +* When using `copyOnEveryStartup`, make sure that the target base directories are created before copying the files + +## [1.1.0] - Nov 3, 2019 +* Updated Artifactory version to 6.14.0 + +## [1.0.1] - Nov 3, 2019 +* Make sure the artifactory pod exits when one of the pre-start stages fail + +## [1.0.0] - Oct 27, 2019 +**IMPORTANT - BREAKING CHANGES!**
+**DOWNTIME MIGHT BE REQUIRED FOR AN UPGRADE!** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), must use the upgrade instructions in [UPGRADE_NOTES.md](UPGRADE_NOTES.md)! +* PostgreSQL sub chart was upgraded to version `6.5.x`. This version is **not backward compatible** with the old version (`0.9.5`)! +* Note the following **PostgreSQL** Helm chart changes + * The chart configuration has changed! See [values.yaml](values.yaml) for the new keys used + * **PostgreSQL** is deployed as a StatefulSet + * See [PostgreSQL helm chart](https://hub.helm.sh/charts/stable/postgresql) for all available configurations + +## [0.17.3] - Oct 24, 2019 +* Change the preStartCommand to support templating + +## [0.17.2] - Oct 21, 2019 +* Add support for setting `artifactory.primary.labels` +* Add support for setting `artifactory.node.labels` +* Add support for setting `nginx.labels` + +## [0.17.1] - Oct 10, 2019 +* Updated Artifactory version to 6.13.1 + +## [0.17.0] - Oct 7, 2019 +* Updated Artifactory version to 6.13.0 + +## [0.16.7] - Sep 24, 2019 +* Option to skip wait-for-db init container with '--set waitForDatabase=false' + +## [0.16.6] - Sep 24, 2019 +* Add support for setting `nginx.service.labels` + +## [0.16.5] - Sep 23, 2019 +* Add support for setting `artifactory.customInitContainersBegin` + +## [0.16.4] - Sep 20, 2019 +* Add support for setting `initContainers.resources` + +## [0.16.3] - Sep 11, 2019 +* Updated Artifactory version to 6.12.2 + +## [0.16.2] - Sep 9, 2019 +* Updated Artifactory version to 6.12.1 + +## [0.16.1] - Aug 22, 2019 +* Fix the nginx server_name directive used with ingress.hosts + +## [0.16.0] - Aug 21, 2019 +* Updated Artifactory version to 6.12.0 + +## [0.15.15] - Aug 18, 2019 +* Fix existingSharedClaim permissions issue and example + +## [0.15.14] - Aug 14, 2019 +* Updated Artifactory version to 6.11.6 + +## [0.15.13] - Aug 11, 2019 +* Fix Ingress routing and add an example + +## [0.15.12] - Aug 6, 2019 +* Do not mount `access/etc/bootstrap.creds` unless user specifies a custom password or secret (Access already generates a random password if not provided one) +* If custom `bootstrap.creds` is provided (using keys or custom secret), prepare it with an init container so the temp file does not persist + +## [0.15.11] - Aug 5, 2019 +* Improve binarystore config + 1. Convert to a secret + 2. Move config to values.yaml + 3. Support an external secret + +## [0.15.10] - Aug 5, 2019 +* Don't create the nginx configmaps when nginx.enabled is false + +## [0.15.9] - Aug 1, 2019 +* Fix masterkey/masterKeySecretName not specified warning render logic in NOTES.txt + +## [0.15.8] - Jul 28, 2019 +* Simplify nginx setup and shorten initial wait for probes + +## [0.15.7] - Jul 25, 2019 +* Updated README about how to apply Artifactory licenses + +## [0.15.6] - Jul 22, 2019 +* Change Ingress API to be compatible with recent kubernetes versions + +## [0.15.5] - Jul 22, 2019 +* Updated Artifactory version to 6.11.3 + +## [0.15.4] - Jul 11, 2019 +* Add `artifactory.customVolumeMounts` support to member node statefulset template + +## [0.15.3] - Jul 11, 2019 +* Add ingress.hosts to the Nginx server_name directive when ingress is enabled to help with Docker repository sub domain configuration + +## [0.15.2] - Jul 3, 2019 +* Add the option for changing nginx config using values.yaml and remove outdated reverse proxy documentation + +## [0.15.1] - Jul 1, 2019 +* Updated Artifactory version to 6.11.1 + +## [0.15.0] - Jun 27, 2019 +* Updated Artifactory version to 6.11.0 and Restart Primary node when bootstrap.creds file has been modified in artifactory-ha + +## [0.14.4] - Jun 24, 2019 +* Add the option to provide an IP for the access-admin endpoints + +## [0.14.3] - Jun 24, 2019 +* Update chart maintainers + +## [0.14.2] - Jun 24, 2019 +* Change Nginx to point to the artifactory externalPort + +## [0.14.1] - Jun 23, 2019 +* Add values files for small, medium and large installations + +## [0.14.0] - Jun 20, 2019 +* Use ConfigMaps for nginx configuration and remove nginx postStart command + +## [0.13.10] - Jun 19, 2019 +* Updated Artifactory version to 6.10.4 + +## [0.13.9] - Jun 18, 2019 +* Add the option to provide additional ingress rules + +## [0.13.8] - Jun 14, 2019 +* Updated readme with improved external database setup example + +## [0.13.7] - Jun 6, 2019 +* Updated Artifactory version to 6.10.3 +* Updated installer-info template + +## [0.13.6] - Jun 6, 2019 +* Updated Google Cloud Storage API URL and https settings + +## [0.13.5] - Jun 5, 2019 +* Delete the db.properties file on Artifactory startup + +## [0.13.4] - Jun 3, 2019 +* Updated Artifactory version to 6.10.2 + +## [0.13.3] - May 21, 2019 +* Updated Artifactory version to 6.10.1 + +## [0.13.2] - May 19, 2019 +* Fix missing logger image tag + +## [0.13.1] - May 15, 2019 +* Support `artifactory.persistence.cacheProviderDir` for on-premise cluster + +## [0.13.0] - May 7, 2019 +* Updated Artifactory version to 6.10.0 + +## [0.12.23] - May 5, 2019 +* Add support for setting `artifactory.async.corePoolSize` + +## [0.12.22] - May 2, 2019 +* Remove unused property `artifactory.releasebundle.feature.enabled` + +## [0.12.21] - Apr 30, 2019 +* Add support for JMX monitoring + +## [0.12.20] - Apr29, 2019 +* Added support for headless services + +## [0.12.19] - Apr 28, 2019 +* Added support for `cacheProviderDir` + +## [0.12.18] - Apr 18, 2019 +* Changing API StatefulSet version to `v1` and permission fix for custom `artifactory.conf` for Nginx + +## [0.12.17] - Apr 16, 2019 +* Updated documentation for Reverse Proxy Configuration + +## [0.12.16] - Apr 12, 2019 +* Added support for `customVolumeMounts` + +## [0.12.15] - Aprl 12, 2019 +* Added support for `bucketExists` flag for googleStorage + +## [0.12.14] - Apr 11, 2019 +* Replace `curl` examples with `wget` due to the new base image + +## [0.12.13] - Aprl 07, 2019 +* Add support for providing the Artifactory license as a parameter + +## [0.12.12] - Apr 10, 2019 +* Updated Artifactory version to 6.9.1 + +## [0.12.11] - Aprl 04, 2019 +* Add support for templated extraEnvironmentVariables + +## [0.12.10] - Aprl 07, 2019 +* Change network policy API group + +## [0.12.9] - Aprl 04, 2019 +* Apply the existing PVC for members (in addition to primary) + +## [0.12.8] - Aprl 03, 2019 +* Bugfix for userPluginSecrets + +## [0.12.7] - Apr 4, 2019 +* Add information about upgrading Artifactory with auto-generated postgres password + +## [0.12.6] - Aprl 03, 2019 +* Added installer info + +## [0.12.5] - Aprl 03, 2019 +* Allow secret names for user plugins to contain template language + +## [0.12.4] - Apr 02, 2019 +* Fix issue #253 (use existing PVC for data and backup storage) + +## [0.12.3] - Apr 02, 2019 +* Allow NetworkPolicy configurations (defaults to allow all) + +## [0.12.2] - Aprl 01, 2019 +* Add support for user plugin secret + +## [0.12.1] - Mar 26, 2019 +* Add the option to copy a list of files to ARTIFACTORY_HOME on startup + +## [0.12.0] - Mar 26, 2019 +* Updated Artifactory version to 6.9.0 + +## [0.11.18] - Mar 25, 2019 +* Add CI tests for persistence, ingress support and nginx + +## [0.11.17] - Mar 22, 2019 +* Add the option to change the default access-admin password + +## [0.11.16] - Mar 22, 2019 +* Added support for `.Probe.path` to customise the paths used for health probes + +## [0.11.15] - Mar 21, 2019 +* Added support for `artifactory.customSidecarContainers` to create custom sidecar containers +* Added support for `artifactory.customVolumes` to create custom volumes + +## [0.11.14] - Mar 21, 2019 +* Make ingress path configurable + +## [0.11.13] - Mar 19, 2019 +* Move the copy of bootstrap config from postStart to preStart for Primary + +## [0.11.12] - Mar 19, 2019 +* Fix existingClaim example + +## [0.11.11] - Mar 18, 2019 +* Disable the option to use nginx PVC with more than one replica + +## [0.11.10] - Mar 15, 2019 +* Wait for nginx configuration file before using it + +## [0.11.9] - Mar 15, 2019 +* Revert securityContext changes since they were causing issues + +## [0.11.8] - Mar 15, 2019 +* Fix issue #247 (init container failing to run) + +## [0.11.7] - Mar 14, 2019 +* Updated Artifactory version to 6.8.7 + +## [0.11.6] - Mar 13, 2019 +* Move securityContext to container level + +## [0.11.5] - Mar 11, 2019 +* Add the option to use existing volume claims for Artifactory storage + +## [0.11.4] - Mar 11, 2019 +* Updated Artifactory version to 6.8.6 + +## [0.11.3] - Mar 5, 2019 +* Updated Artifactory version to 6.8.4 + +## [0.11.2] - Mar 4, 2019 +* Add support for catalina logs sidecars + +## [0.11.1] - Feb 27, 2019 +* Updated Artifactory version to 6.8.3 + +## [0.11.0] - Feb 25, 2019 +* Add nginx support for tail sidecars + +## [0.10.3] - Feb 21, 2019 +* Add s3AwsVersion option to awsS3 configuration for use with IAM roles + +## [0.10.2] - Feb 19, 2019 +* Updated Artifactory version to 6.8.2 + +## [0.10.1] - Feb 17, 2019 +* Updated Artifactory version to 6.8.1 +* Add example of `SERVER_XML_EXTRA_CONNECTOR` usage + +## [0.10.0] - Feb 15, 2019 +* Updated Artifactory version to 6.8.0 + +## [0.9.7] - Feb 13, 2019 +* Updated Artifactory version to 6.7.3 + +## [0.9.6] - Feb 7, 2019 +* Add support for tail sidecars to view logs from k8s api + +## [0.9.5] - Feb 6, 2019 +* Fix support for customizing statefulset `terminationGracePeriodSeconds` + +## [0.9.4] - Feb 5, 2019 +* Add support for customizing statefulset `terminationGracePeriodSeconds` + +## [0.9.3] - Feb 5, 2019 +* Remove the inactive server remove plugin + +## [0.9.2] - Feb 3, 2019 +* Updated Artifactory version to 6.7.2 + +## [0.9.1] - Jan 27, 2019 +* Fix support for Azure Blob Storage Binary provider + +## [0.9.0] - Jan 23, 2019 +* Updated Artifactory version to 6.7.0 + +## [0.8.10] - Jan 22, 2019 +* Added support for `artifactory.customInitContainers` to create custom init containers + +## [0.8.9] - Jan 18, 2019 +* Added support of values ingress.labels + +## [0.8.8] - Jan 16, 2019 +* Mount replicator.yaml (config) directly to /replicator_extra_conf + +## [0.8.7] - Jan 15, 2018 +* Add support for Azure Blob Storage Binary provider + +## [0.8.6] - Jan 13, 2019 +* Fix documentation about nginx group id + +## [0.8.5] - Jan 13, 2019 +* Updated Artifactory version to 6.6.5 + +## [0.8.4] - Jan 8, 2019 +* Make artifactory.replicator.publicUrl required when the replicator is enabled + +## [0.8.3] - Jan 1, 2019 +* Updated Artifactory version to 6.6.3 +* Add support for `artifactory.extraEnvironmentVariables` to pass more environment variables to Artifactory + +## [0.8.2] - Dec 28, 2018 +* Fix location `replicator.yaml` is copied to + +## [0.8.1] - Dec 27, 2018 +* Updated Artifactory version to 6.6.1 + +## [0.8.0] - Dec 20, 2018 +* Updated Artifactory version to 6.6.0 + +## [0.7.17] - Dec 17, 2018 +* Updated Artifactory version to 6.5.13 + +## [0.7.16] - Dec 12, 2018 +* Fix documentation about Artifactory license setup using secret + +## [0.7.15] - Dec 9, 2018 +* AWS S3 add `roleName` for using IAM role + +## [0.7.14] - Dec 6, 2018 +* AWS S3 `identity` and `credential` are now added only if have a value to allow using IAM role + +## [0.7.13] - Dec 5, 2018 +* Remove Distribution certificates creation. + +## [0.7.12] - Dec 2, 2018 +* Remove Java option "-Dartifactory.locking.provider.type=db". This is already the default setting. + +## [0.7.11] - Nov 30, 2018 +* Updated Artifactory version to 6.5.9 + +## [0.7.10] - Nov 29, 2018 +* Fixed the volumeMount for the replicator.yaml + +## [0.7.9] - Nov 29, 2018 +* Optionally include primary node into poddisruptionbudget + +## [0.7.8] - Nov 29, 2018 +* Updated postgresql version to 9.6.11 + +## [0.7.7] - Nov 27, 2018 +* Updated Artifactory version to 6.5.8 + +## [0.7.6] - Nov 18, 2018 +* Added support for configMap to use custom Reverse Proxy Configuration with Nginx + +## [0.7.5] - Nov 14, 2018 +* Updated Artifactory version to 6.5.3 + +## [0.7.4] - Nov 13, 2018 +* Allow pod anti-affinity settings to include primary node + +## [0.7.3] - Nov 12, 2018 +* Support artifactory.preStartCommand for running command before entrypoint starts + +## [0.7.2] - Nov 7, 2018 +* Support database.url parameter (DB_URL) + +## [0.7.1] - Oct 29, 2018 +* Change probes port to 8040 (so they will not be blocked when all tomcat threads on 8081 are exhausted) + +## [0.7.0] - Oct 28, 2018 +* Update postgresql chart to version 0.9.5 to be able and use `postgresConfig` options + +## [0.6.9] - Oct 23, 2018 +* Fix providing external secret for database credentials + +## [0.6.8] - Oct 22, 2018 +* Allow user to configure externalTrafficPolicy for Loadbalancer + +## [0.6.7] - Oct 22, 2018 +* Updated ingress annotation support (with examples) to support docker registry v2 + +## [0.6.6] - Oct 21, 2018 +* Updated Artifactory version to 6.5.2 + +## [0.6.5] - Oct 19, 2018 +* Allow providing pre-existing secret containing master key +* Allow arbitrary annotations on primary and member node pods +* Enforce size limits when using local storage with `emptyDir` +* Allow `soft` or `hard` specification of member node anti-affinity +* Allow providing pre-existing secrets containing external database credentials +* Fix `s3` binary store provider to properly use the `cache-fs` provider +* Allow arbitrary properties when using the `s3` binary store provider + +## [0.6.4] - Oct 18, 2018 +* Updated Artifactory version to 6.5.1 + +## [0.6.3] - Oct 17, 2018 +* Add Apache 2.0 license + +## [0.6.2] - Oct 14, 2018 +* Make S3 endpoint configurable (was hardcoded with `s3.amazonaws.com`) + +## [0.6.1] - Oct 11, 2018 +* Allows ingress default `backend` to be enabled or disabled (defaults to enabled) + +## [0.6.0] - Oct 11, 2018 +* Updated Artifactory version to 6.5.0 + +## [0.5.3] - Oct 9, 2018 +* Quote ingress hosts to support wildcard names + +## [0.5.2] - Oct 2, 2018 +* Add `helm repo add jfrog https://charts.jfrog.io` to README + +## [0.5.1] - Oct 2, 2018 +* Set Artifactory to 6.4.1 + +## [0.5.0] - Sep 27, 2018 +* Set Artifactory to 6.4.0 + +## [0.4.7] - Sep 26, 2018 +* Add ci/test-values.yaml + +## [0.4.6] - Sep 25, 2018 +* Add PodDisruptionBudget for member nodes, defaulting to minAvailable of 1 + +## [0.4.4] - Sep 2, 2018 +* Updated Artifactory version to 6.3.2 + +## [0.4.0] - Aug 22, 2018 +* Added support to run as non root +* Updated Artifactory version to 6.2.0 + +## [0.3.0] - Aug 22, 2018 +* Enabled RBAC Support +* Added support for PostStartCommand (To download Database JDBC connector) +* Increased postgresql max_connections +* Added support for `nginx.conf` ConfigMap +* Updated Artifactory version to 6.1.0 diff --git a/charts/jfrog/artifactory-ha/107.90.9/Chart.lock b/charts/jfrog/artifactory-ha/107.90.9/Chart.lock new file mode 100644 index 0000000000..eb94099719 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.jfrog.io/ + version: 10.3.18 +digest: sha256:404ce007353baaf92a6c5f24b249d5b336c232e5fd2c29f8a0e4d0095a09fd53 +generated: "2022-03-08T08:54:51.805126+05:30" diff --git a/charts/jfrog/artifactory-ha/107.90.9/Chart.yaml b/charts/jfrog/artifactory-ha/107.90.9/Chart.yaml new file mode 100644 index 0000000000..a5daa4ecc9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/Chart.yaml @@ -0,0 +1,30 @@ +annotations: + artifactoryServiceVersion: 7.90.12 + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: JFrog Artifactory HA + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: artifactory-ha +apiVersion: v2 +appVersion: 7.90.9 +dependencies: +- condition: postgresql.enabled + name: postgresql + repository: file://./charts/postgresql + version: 10.3.18 +description: Universal Repository Manager supporting all major packaging formats, + build tools and CI servers. +home: https://www.jfrog.com/artifactory/ +icon: file://assets/icons/artifactory-ha.png +keywords: +- artifactory +- jfrog +- devops +kubeVersion: '>= 1.19.0-0' +maintainers: +- email: installers@jfrog.com + name: Chart Maintainers at JFrog +name: artifactory-ha +sources: +- https://github.com/jfrog/charts +type: application +version: 107.90.9 diff --git a/charts/jfrog/artifactory-ha/107.90.9/LICENSE b/charts/jfrog/artifactory-ha/107.90.9/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/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/charts/jfrog/artifactory-ha/107.90.9/README.md b/charts/jfrog/artifactory-ha/107.90.9/README.md new file mode 100644 index 0000000000..49155926e0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/README.md @@ -0,0 +1,69 @@ +# JFrog Artifactory High Availability Helm Chart + +**IMPORTANT!** Our Helm Chart docs have moved to our main documentation site. Below you will find the basic instructions for installing, uninstalling, and deleting Artifactory. For all other information, refer to [Installing Artifactory - Helm HA Installation](https://www.jfrog.com/confluence/display/JFROG/Installing+Artifactory#InstallingArtifactory-HelmHAInstallation). + +**Note:** From Artifactory 7.17.4 and above, the Helm HA installation can be installed so that each node you install can run all tasks in the cluster. + +Below you will find the basic instructions for installing, uninstalling, and deleting Artifactory. For all other information, refer to the documentation site. + +## Prerequisites Details + +* Kubernetes 1.19+ +* Artifactory HA license + +## Chart Details +This chart will do the following: + +* Deploy Artifactory highly available cluster. 1 primary node and 2 member nodes. +* Deploy a PostgreSQL database **NOTE:** For production grade installations it is recommended to use an external PostgreSQL +* Deploy an Nginx server + +## Installing the Chart + +### Add JFrog Helm repository + +Before installing JFrog helm charts, you need to add the [JFrog helm repository](https://charts.jfrog.io) to your helm client + +```bash +helm repo add jfrog https://charts.jfrog.io +``` +2. Next, create a unique Master Key (Artifactory requires a unique master key) and pass it to the template during installation. +3. Now, update the repository. + +```bash +helm repo update +``` + +### Install Chart +To install the chart with the release name `artifactory`: +```bash +helm upgrade --install artifactory-ha jfrog/artifactory-ha --namespace artifactory-ha --create-namespace +``` + +### Apply Sizing configurations to the Chart +To apply the chart with recommended sizing configurations : +For small configurations : +```bash +helm upgrade --install artifactory-ha jfrog/artifactory-ha -f sizing/artifactory-small-extra-config.yaml -f sizing/artifactory-small.yaml --namespace artifactory-ha --create-namespace +``` + +## Uninstalling Artifactory + +Uninstall is supported only on Helm v3 and on. + +Uninstall Artifactory using the following command. + +```bash +helm uninstall artifactory-ha && sleep 90 && kubectl delete pvc -l app=artifactory-ha +``` + +## Deleting Artifactory + +**IMPORTANT:** Deleting Artifactory will also delete your data volumes and you will lose all of your data. You must back up all this information before deletion. You do not need to uninstall Artifactory before deleting it. + +To delete Artifactory use the following command. + +```bash +helm delete artifactory-ha --namespace artifactory-ha +``` + diff --git a/charts/jfrog/artifactory-ha/107.90.9/app-readme.md b/charts/jfrog/artifactory-ha/107.90.9/app-readme.md new file mode 100644 index 0000000000..a5aa5fd478 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/app-readme.md @@ -0,0 +1,16 @@ +# JFrog Artifactory High Availability Helm Chart + +Universal Repository Manager supporting all major packaging formats, build tools and CI servers. + +## Chart Details +This chart will do the following: + +* Deploy Artifactory highly available cluster. 1 primary node and 2 member nodes. +* Deploy a PostgreSQL database +* Deploy an Nginx server(optional) + +## Useful links +Blog: [Herd Trust Into Your Rancher Labs Multi-Cloud Strategy with Artifactory](https://jfrog.com/blog/herd-trust-into-your-rancher-labs-multi-cloud-strategy-with-artifactory/) + +## Activate Your Artifactory Instance +Don't have a license? Please send an email to [rancher-jfrog-licenses@jfrog.com](mailto:rancher-jfrog-licenses@jfrog.com) to get it. diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/.helmignore b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/.helmignore new file mode 100644 index 0000000000..f0c1319444 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.lock b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.lock new file mode 100644 index 0000000000..3687f52df5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.4.2 +digest: sha256:dce0349883107e3ff103f4f17d3af4ad1ea3c7993551b1c28865867d3e53d37c +generated: "2021-03-30T09:13:28.360322819Z" diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.yaml new file mode 100644 index 0000000000..4b197b2071 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/Chart.yaml @@ -0,0 +1,29 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 11.11.0 +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.x.x +description: Chart for PostgreSQL, an object-relational database management system + (ORDBMS) with an emphasis on extensibility and on standards-compliance. +home: https://github.com/bitnami/charts/tree/master/bitnami/postgresql +icon: https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-220x234.png +keywords: +- postgresql +- postgres +- database +- sql +- replication +- cluster +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: cedric@desaintmartin.fr + name: desaintmartin +name: postgresql +sources: +- https://github.com/bitnami/bitnami-docker-postgresql +- https://www.postgresql.org/ +version: 10.3.18 diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/README.md b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/README.md new file mode 100644 index 0000000000..63d3605bb8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/README.md @@ -0,0 +1,770 @@ +# PostgreSQL + +[PostgreSQL](https://www.postgresql.org/) is an object-relational database management system (ORDBMS) with an emphasis on extensibility and on standards-compliance. + +For HA, please see [this repo](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha) + +## TL;DR + +```console +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/postgresql +``` + +## Introduction + +This chart bootstraps a [PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This chart has been tested to work with NGINX Ingress, cert-manager, fluentd and Prometheus on top of the [BKPR](https://kubeprod.io/). + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.1.0 +- PV provisioner support in the underlying infrastructure + +## Installing the Chart +To install the chart with the release name `my-release`: + +```console +$ helm install my-release bitnami/postgresql +``` + +The command deploys PostgreSQL on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```console +$ helm delete my-release +``` + +The command removes all the Kubernetes components but PVC's associated with the chart and deletes the release. + +To delete the PVC's associated with `my-release`: + +```console +$ kubectl delete pvc -l release=my-release +``` + +> **Note**: Deleting the PVC's will delete postgresql data as well. Please be cautious before doing it. + +## Parameters + +The following tables lists the configurable parameters of the PostgreSQL chart and their default values. + +| Parameter | Description | Default | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| `global.imageRegistry` | Global Docker Image registry | `nil` | +| `global.postgresql.postgresqlDatabase` | PostgreSQL database (overrides `postgresqlDatabase`) | `nil` | +| `global.postgresql.postgresqlUsername` | PostgreSQL username (overrides `postgresqlUsername`) | `nil` | +| `global.postgresql.existingSecret` | Name of existing secret to use for PostgreSQL passwords (overrides `existingSecret`) | `nil` | +| `global.postgresql.postgresqlPassword` | PostgreSQL admin password (overrides `postgresqlPassword`) | `nil` | +| `global.postgresql.servicePort` | PostgreSQL port (overrides `service.port`) | `nil` | +| `global.postgresql.replicationPassword` | Replication user password (overrides `replication.password`) | `nil` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| `image.registry` | PostgreSQL Image registry | `docker.io` | +| `image.repository` | PostgreSQL Image name | `bitnami/postgresql` | +| `image.tag` | PostgreSQL Image tag | `{TAG_NAME}` | +| `image.pullPolicy` | PostgreSQL Image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify Image pull secrets | `nil` (does not add image pull secrets to deployed pods) | +| `image.debug` | Specify if debug values should be set | `false` | +| `nameOverride` | String to partially override common.names.fullname template with a string (will prepend the release name) | `nil` | +| `fullnameOverride` | String to fully override common.names.fullname template with a string | `nil` | +| `volumePermissions.enabled` | Enable init container that changes volume permissions in the data directory (for cases where the default k8s `runAsUser` and `fsUser` values do not work) | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `"10"` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | +| `volumePermissions.securityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `volumePermissions.securityContext.runAsUser` | User ID for the init container (when facing issues in OpenShift or uid unknown, try value "auto") | `0` | +| `usePasswordFile` | Have the secrets mounted as a file instead of env vars | `false` | +| `ldap.enabled` | Enable LDAP support | `false` | +| `ldap.existingSecret` | Name of existing secret to use for LDAP passwords | `nil` | +| `ldap.url` | LDAP URL beginning in the form `ldap[s]://host[:port]/basedn[?[attribute][?[scope][?[filter]]]]` | `nil` | +| `ldap.server` | IP address or name of the LDAP server. | `nil` | +| `ldap.port` | Port number on the LDAP server to connect to | `nil` | +| `ldap.scheme` | Set to `ldaps` to use LDAPS. | `nil` | +| `ldap.tls` | Set to `1` to use TLS encryption | `nil` | +| `ldap.prefix` | String to prepend to the user name when forming the DN to bind | `nil` | +| `ldap.suffix` | String to append to the user name when forming the DN to bind | `nil` | +| `ldap.search_attr` | Attribute to match against the user name in the search | `nil` | +| `ldap.search_filter` | The search filter to use when doing search+bind authentication | `nil` | +| `ldap.baseDN` | Root DN to begin the search for the user in | `nil` | +| `ldap.bindDN` | DN of user to bind to LDAP | `nil` | +| `ldap.bind_password` | Password for the user to bind to LDAP | `nil` | +| `replication.enabled` | Enable replication | `false` | +| `replication.user` | Replication user | `repl_user` | +| `replication.password` | Replication user password | `repl_password` | +| `replication.readReplicas` | Number of read replicas replicas | `1` | +| `replication.synchronousCommit` | Set synchronous commit mode. Allowed values: `on`, `remote_apply`, `remote_write`, `local` and `off` | `off` | +| `replication.numSynchronousReplicas` | Number of replicas that will have synchronous replication. Note: Cannot be greater than `replication.readReplicas`. | `0` | +| `replication.applicationName` | Cluster application name. Useful for advanced replication settings | `my_application` | +| `existingSecret` | Name of existing secret to use for PostgreSQL passwords. The secret has to contain the keys `postgresql-password` which is the password for `postgresqlUsername` when it is different of `postgres`, `postgresql-postgres-password` which will override `postgresqlPassword`, `postgresql-replication-password` which will override `replication.password` and `postgresql-ldap-password` which will be used to authenticate on LDAP. The value is evaluated as a template. | `nil` | +| `postgresqlPostgresPassword` | PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`, in which case`postgres` is the admin username). | _random 10 character alphanumeric string_ | +| `postgresqlUsername` | PostgreSQL user (creates a non-admin user when `postgresqlUsername` is not `postgres`) | `postgres` | +| `postgresqlPassword` | PostgreSQL user password | _random 10 character alphanumeric string_ | +| `postgresqlDatabase` | PostgreSQL database | `nil` | +| `postgresqlDataDir` | PostgreSQL data dir folder | `/bitnami/postgresql` (same value as persistence.mountPath) | +| `extraEnv` | Any extra environment variables you would like to pass on to the pod. The value is evaluated as a template. | `[]` | +| `extraEnvVarsCM` | Name of a Config Map containing extra environment variables you would like to pass on to the pod. The value is evaluated as a template. | `nil` | +| `postgresqlInitdbArgs` | PostgreSQL initdb extra arguments | `nil` | +| `postgresqlInitdbWalDir` | PostgreSQL location for transaction log | `nil` | +| `postgresqlConfiguration` | Runtime Config Parameters | `nil` | +| `postgresqlExtendedConf` | Extended Runtime Config Parameters (appended to main or default configuration) | `nil` | +| `pgHbaConfiguration` | Content of pg_hba.conf | `nil (do not create pg_hba.conf)` | +| `postgresqlSharedPreloadLibraries` | Shared preload libraries (comma-separated list) | `pgaudit` | +| `postgresqlMaxConnections` | Maximum total connections | `nil` | +| `postgresqlPostgresConnectionLimit` | Maximum total connections for the postgres user | `nil` | +| `postgresqlDbUserConnectionLimit` | Maximum total connections for the non-admin user | `nil` | +| `postgresqlTcpKeepalivesInterval` | TCP keepalives interval | `nil` | +| `postgresqlTcpKeepalivesIdle` | TCP keepalives idle | `nil` | +| `postgresqlTcpKeepalivesCount` | TCP keepalives count | `nil` | +| `postgresqlStatementTimeout` | Statement timeout | `nil` | +| `postgresqlPghbaRemoveFilters` | Comma-separated list of patterns to remove from the pg_hba.conf file | `nil` | +| `customStartupProbe` | Override default startup probe | `nil` | +| `customLivenessProbe` | Override default liveness probe | `nil` | +| `customReadinessProbe` | Override default readiness probe | `nil` | +| `audit.logHostname` | Add client hostnames to the log file | `false` | +| `audit.logConnections` | Add client log-in operations to the log file | `false` | +| `audit.logDisconnections` | Add client log-outs operations to the log file | `false` | +| `audit.pgAuditLog` | Add operations to log using the pgAudit extension | `nil` | +| `audit.clientMinMessages` | Message log level to share with the user | `nil` | +| `audit.logLinePrefix` | Template string for the log line prefix | `nil` | +| `audit.logTimezone` | Timezone for the log timestamps | `nil` | +| `configurationConfigMap` | ConfigMap with the PostgreSQL configuration files (Note: Overrides `postgresqlConfiguration` and `pgHbaConfiguration`). The value is evaluated as a template. | `nil` | +| `extendedConfConfigMap` | ConfigMap with the extended PostgreSQL configuration files. The value is evaluated as a template. | `nil` | +| `initdbScripts` | Dictionary of initdb scripts | `nil` | +| `initdbUser` | PostgreSQL user to execute the .sql and sql.gz scripts | `nil` | +| `initdbPassword` | Password for the user specified in `initdbUser` | `nil` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`). The value is evaluated as a template. | `nil` | +| `initdbScriptsSecret` | Secret with initdb scripts that contain sensitive information (Note: can be used with `initdbScriptsConfigMap` or `initdbScripts`). The value is evaluated as a template. | `nil` | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | PostgreSQL port | `5432` | +| `service.nodePort` | Kubernetes Service nodePort | `nil` | +| `service.annotations` | Annotations for PostgreSQL service | `{}` (evaluated as a template) | +| `service.loadBalancerIP` | loadBalancerIP if service type is `LoadBalancer` | `nil` | +| `service.loadBalancerSourceRanges` | Address that are allowed when svc is LoadBalancer | `[]` (evaluated as a template) | +| `schedulerName` | Name of the k8s scheduler (other than default) | `nil` | +| `shmVolume.enabled` | Enable emptyDir volume for /dev/shm for primary and read replica(s) Pod(s) | `true` | +| `shmVolume.chmod.enabled` | Run at init chmod 777 of the /dev/shm (ignored if `volumePermissions.enabled` is `false`) | `true` | +| `persistence.enabled` | Enable persistence using PVC | `true` | +| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim`, the value is evaluated as a template. | `nil` | +| `persistence.mountPath` | Path to mount the volume at | `/bitnami/postgresql` | +| `persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `persistence.storageClass` | PVC Storage Class for PostgreSQL volume | `nil` | +| `persistence.accessModes` | PVC Access Mode for PostgreSQL volume | `[ReadWriteOnce]` | +| `persistence.size` | PVC Storage Request for PostgreSQL volume | `8Gi` | +| `persistence.annotations` | Annotations for the PVC | `{}` | +| `persistence.selector` | Selector to match an existing Persistent Volume (this value is evaluated as a template) | `{}` | +| `commonAnnotations` | Annotations to be added to all deployed resources (rendered as a template) | `{}` | +| `primary.podAffinityPreset` | PostgreSQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.podAntiAffinityPreset` | PostgreSQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `primary.nodeAffinityPreset.type` | PostgreSQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.nodeAffinityPreset.key` | PostgreSQL primary node label key to match Ignored if `primary.affinity` is set. | `""` | +| `primary.nodeAffinityPreset.values` | PostgreSQL primary node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `primary.affinity` | Affinity for PostgreSQL primary pods assignment | `{}` (evaluated as a template) | +| `primary.nodeSelector` | Node labels for PostgreSQL primary pods assignment | `{}` (evaluated as a template) | +| `primary.tolerations` | Tolerations for PostgreSQL primary pods assignment | `[]` (evaluated as a template) | +| `primary.anotations` | Map of annotations to add to the statefulset (postgresql primary) | `{}` | +| `primary.labels` | Map of labels to add to the statefulset (postgresql primary) | `{}` | +| `primary.podAnnotations` | Map of annotations to add to the pods (postgresql primary) | `{}` | +| `primary.podLabels` | Map of labels to add to the pods (postgresql primary) | `{}` | +| `primary.priorityClassName` | Priority Class to use for each pod (postgresql primary) | `nil` | +| `primary.extraInitContainers` | Additional init containers to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumes` | Additional volumes to add to the pods (postgresql primary) | `[]` | +| `primary.sidecars` | Add additional containers to the pod | `[]` | +| `primary.service.type` | Allows using a different service type for primary | `nil` | +| `primary.service.nodePort` | Allows using a different nodePort for primary | `nil` | +| `primary.service.clusterIP` | Allows using a different clusterIP for primary | `nil` | +| `primaryAsStandBy.enabled` | Whether to enable current cluster's primary as standby server of another cluster or not. | `false` | +| `primaryAsStandBy.primaryHost` | The Host of replication primary in the other cluster. | `nil` | +| `primaryAsStandBy.primaryPort ` | The Port of replication primary in the other cluster. | `nil` | +| `readReplicas.podAffinityPreset` | PostgreSQL read only pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.podAntiAffinityPreset` | PostgreSQL read only pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `readReplicas.nodeAffinityPreset.type` | PostgreSQL read only node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.nodeAffinityPreset.key` | PostgreSQL read only node label key to match Ignored if `primary.affinity` is set. | `""` | +| `readReplicas.nodeAffinityPreset.values` | PostgreSQL read only node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `readReplicas.affinity` | Affinity for PostgreSQL read only pods assignment | `{}` (evaluated as a template) | +| `readReplicas.nodeSelector` | Node labels for PostgreSQL read only pods assignment | `{}` (evaluated as a template) | +| `readReplicas.anotations` | Map of annotations to add to the statefulsets (postgresql readReplicas) | `{}` | +| `readReplicas.resources` | CPU/Memory resource requests/limits override for readReplicass. Will fallback to `values.resources` if not defined. | `{}` | +| `readReplicas.labels` | Map of labels to add to the statefulsets (postgresql readReplicas) | `{}` | +| `readReplicas.podAnnotations` | Map of annotations to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.podLabels` | Map of labels to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.priorityClassName` | Priority Class to use for each pod (postgresql readReplicas) | `nil` | +| `readReplicas.extraInitContainers` | Additional init containers to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumes` | Additional volumes to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.sidecars` | Add additional containers to the pod | `[]` | +| `readReplicas.service.type` | Allows using a different service type for readReplicas | `nil` | +| `readReplicas.service.nodePort` | Allows using a different nodePort for readReplicas | `nil` | +| `readReplicas.service.clusterIP` | Allows using a different clusterIP for readReplicas | `nil` | +| `readReplicas.persistence.enabled` | Whether to enable readReplicas replicas persistence | `true` | +| `terminationGracePeriodSeconds` | Seconds the pod needs to terminate gracefully | `nil` | +| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `250m` | +| `securityContext.*` | Other pod security context to be included as-is in the pod spec | `{}` | +| `securityContext.enabled` | Enable security context | `true` | +| `securityContext.fsGroup` | Group ID for the pod | `1001` | +| `containerSecurityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `containerSecurityContext.enabled` | Enable container security context | `true` | +| `containerSecurityContext.runAsUser` | User ID for the container | `1001` | +| `serviceAccount.enabled` | Enable service account (Note: Service Account will only be automatically created if `serviceAccount.name` is not set) | `false` | +| `serviceAccount.name` | Name of existing service account | `nil` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.explicitNamespacesSelector` | A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed | `{}` | +| `startupProbe.enabled` | Enable startupProbe | `false` | +| `startupProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `startupProbe.periodSeconds` | How often to perform the probe | 15 | +| `startupProbe.timeoutSeconds` | When the probe times | 5 | +| `startupProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | +| `startupProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | +| `livenessProbe.enabled` | Enable livenessProbe | `true` | +| `livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `livenessProbe.periodSeconds` | How often to perform the probe | 10 | +| `livenessProbe.timeoutSeconds` | When the probe times out | 5 | +| `livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `readinessProbe.enabled` | Enable readinessProbe | `true` | +| `readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | 5 | +| `readinessProbe.periodSeconds` | How often to perform the probe | 10 | +| `readinessProbe.timeoutSeconds` | When the probe times out | 5 | +| `readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `tls.enabled` | Enable TLS traffic support | `false` | +| `tls.preferServerCiphers` | Whether to use the server's TLS cipher preferences rather than the client's | `true` | +| `tls.certificatesSecret` | Name of an existing secret that contains the certificates | `nil` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename. If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate. | `nil` | +| `tls.crlFilename` | File containing a Certificate Revocation List | `nil` | +| `metrics.enabled` | Start a prometheus exporter | `false` | +| `metrics.service.type` | Kubernetes Service type | `ClusterIP` | +| `service.clusterIP` | Static clusterIP or None for headless services | `nil` | +| `metrics.service.annotations` | Additional annotations for metrics exporter pod | `{ prometheus.io/scrape: "true", prometheus.io/port: "9187"}` | +| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` | +| `metrics.serviceMonitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` | +| `metrics.serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.namespace` | Optional namespace in which to create ServiceMonitor | `nil` | +| `metrics.serviceMonitor.interval` | Scrape interval. If not set, the Prometheus default scrape interval is used | `nil` | +| `metrics.serviceMonitor.scrapeTimeout` | Scrape timeout. If not set, the Prometheus default scrape timeout is used | `nil` | +| `metrics.prometheusRule.enabled` | Set this to true to create prometheusRules for Prometheus operator | `false` | +| `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so prometheusRules will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.namespace` | namespace where prometheusRules resource should be created | the same namespace as postgresql | +| `metrics.prometheusRule.rules` | [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) to be created, check values for an example. | `[]` | +| `metrics.image.registry` | PostgreSQL Exporter Image registry | `docker.io` | +| `metrics.image.repository` | PostgreSQL Exporter Image name | `bitnami/postgres-exporter` | +| `metrics.image.tag` | PostgreSQL Exporter Image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | PostgreSQL Exporter Image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify Image pull secrets | `nil` (does not add image pull secrets to deployed pods) | +| `metrics.customMetrics` | Additional custom metrics | `nil` | +| `metrics.extraEnvVars` | Extra environment variables to add to exporter | `{}` (evaluated as a template) | +| `metrics.securityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `metrics.securityContext.enabled` | Enable security context for metrics | `false` | +| `metrics.securityContext.runAsUser` | User ID for the container for metrics | `1001` | +| `metrics.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `metrics.livenessProbe.periodSeconds` | How often to perform the probe | 10 | +| `metrics.livenessProbe.timeoutSeconds` | When the probe times out | 5 | +| `metrics.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `metrics.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `metrics.readinessProbe.enabled` | would you like a readinessProbe to be enabled | `true` | +| `metrics.readinessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 5 | +| `metrics.readinessProbe.periodSeconds` | How often to perform the probe | 10 | +| `metrics.readinessProbe.timeoutSeconds` | When the probe times out | 5 | +| `metrics.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `metrics.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `updateStrategy` | Update strategy policy | `{type: "RollingUpdate"}` | +| `psp.create` | Create Pod Security Policy | `false` | +| `rbac.create` | Create Role and RoleBinding (required for PSP to work) | `false` | +| `extraDeploy` | Array of extra objects to deploy with the release (evaluated as a template). | `nil` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +$ helm install my-release \ + --set postgresqlPassword=secretpassword,postgresqlDatabase=my-database \ + bitnami/postgresql +``` + +The above command sets the PostgreSQL `postgres` account password to `secretpassword`. Additionally it creates a database named `my-database`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +$ helm install my-release -f values.yaml bitnami/postgresql +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Configuration and installation details + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Customizing primary and read replica services in a replicated configuration + +At the top level, there is a service object which defines the services for both primary and readReplicas. For deeper customization, there are service objects for both the primary and read types individually. This allows you to override the values in the top level service object so that the primary and read can be of different service types and with different clusterIPs / nodePorts. Also in the case you want the primary and read to be of type nodePort, you will need to set the nodePorts to different values to prevent a collision. The values that are deeper in the primary.service or readReplicas.service objects will take precedence over the top level service object. + +### Change PostgreSQL version + +To modify the PostgreSQL version used in this chart you can specify a [valid image tag](https://hub.docker.com/r/bitnami/postgresql/tags/) using the `image.tag` parameter. For example, `image.tag=X.Y.Z`. This approach is also applicable to other images like exporters. + +### postgresql.conf / pg_hba.conf files as configMap + +This helm chart also supports to customize the whole configuration file. + +Add your custom file to "files/postgresql.conf" in your working directory. This file will be mounted as configMap to the containers and it will be used for configuring the PostgreSQL server. + +Alternatively, you can add additional PostgreSQL configuration parameters using the `postgresqlExtendedConf` parameter as a dict, using camelCase, e.g. {"sharedBuffers": "500MB"}. Alternatively, to replace the entire default configuration use `postgresqlConfiguration`. + +In addition to these options, you can also set an external ConfigMap with all the configuration files. This is done by setting the `configurationConfigMap` parameter. Note that this will override the two previous options. + +### Allow settings to be loaded from files other than the default `postgresql.conf` + +If you don't want to provide the whole PostgreSQL configuration file and only specify certain parameters, you can add your extended `.conf` files to "files/conf.d/" in your working directory. +Those files will be mounted as configMap to the containers adding/overwriting the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +Alternatively, you can also set an external ConfigMap with all the extra configuration files. This is done by setting the `extendedConfConfigMap` parameter. Note that this will override the previous option. + +### Initialize a fresh instance + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, they must be located inside the chart folder `files/docker-entrypoint-initdb.d` so they can be consumed as a ConfigMap. + +Alternatively, you can specify custom scripts using the `initdbScripts` parameter as dict. + +In addition to these options, you can also set an external ConfigMap with all the initialization scripts. This is done by setting the `initdbScriptsConfigMap` parameter. Note that this will override the two previous options. If your initialization scripts contain sensitive information such as credentials or passwords, you can use the `initdbScriptsSecret` parameter. + +The allowed extensions are `.sh`, `.sql` and `.sql.gz`. + +### Securing traffic using TLS + +TLS support can be enabled in the chart by specifying the `tls.` parameters while creating a release. The following parameters should be configured to properly enable the TLS support in the chart: + +- `tls.enabled`: Enable TLS support. Defaults to `false` +- `tls.certificatesSecret`: Name of an existing secret that contains the certificates. No defaults. +- `tls.certFilename`: Certificate filename. No defaults. +- `tls.certKeyFilename`: Certificate key filename. No defaults. + +For example: + +* First, create the secret with the cetificates files: + + ```console + kubectl create secret generic certificates-tls-secret --from-file=./cert.crt --from-file=./cert.key --from-file=./ca.crt + ``` + +* Then, use the following parameters: + + ```console + volumePermissions.enabled=true + tls.enabled=true + tls.certificatesSecret="certificates-tls-secret" + tls.certFilename="cert.crt" + tls.certKeyFilename="cert.key" + ``` + + > Note TLS and VolumePermissions: PostgreSQL requires certain permissions on sensitive files (such as certificate keys) to start up. Due to an on-going [issue](https://github.com/kubernetes/kubernetes/issues/57923) regarding kubernetes permissions and the use of `containerSecurityContext.runAsUser`, you must enable `volumePermissions` to ensure everything works as expected. + +### Sidecars + +If you need additional containers to run within the same pod as PostgreSQL (e.g. an additional metrics or logging exporter), you can do so via the `sidecars` config parameter. Simply define your container according to the Kubernetes container spec. + +```yaml +# For the PostgreSQL primary +primary: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +# For the PostgreSQL replicas +readReplicas: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +### Metrics + +The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9187) is not exposed and it is expected that the metrics are collected from inside the k8s cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). + +The exporter allows to create custom metrics from additional SQL queries. See the Chart's `values.yaml` for an example and consult the [exporters documentation](https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file) for more details. + +### Use of global variables + +In more complex scenarios, we may have the following tree of dependencies + +``` + +--------------+ + | | + +------------+ Chart 1 +-----------+ + | | | | + | --------+------+ | + | | | + | | | + | | | + | | | + v v v ++-------+------+ +--------+------+ +--------+------+ +| | | | | | +| PostgreSQL | | Sub-chart 1 | | Sub-chart 2 | +| | | | | | ++--------------+ +---------------+ +---------------+ +``` + +The three charts below depend on the parent chart Chart 1. However, subcharts 1 and 2 may need to connect to PostgreSQL as well. In order to do so, subcharts 1 and 2 need to know the PostgreSQL credentials, so one option for deploying could be deploy Chart 1 with the following parameters: + +``` +postgresql.postgresqlPassword=testtest +subchart1.postgresql.postgresqlPassword=testtest +subchart2.postgresql.postgresqlPassword=testtest +postgresql.postgresqlDatabase=db1 +subchart1.postgresql.postgresqlDatabase=db1 +subchart2.postgresql.postgresqlDatabase=db1 +``` + +If the number of dependent sub-charts increases, installing the chart with parameters can become increasingly difficult. An alternative would be to set the credentials using global variables as follows: + +``` +global.postgresql.postgresqlPassword=testtest +global.postgresql.postgresqlDatabase=db1 +``` + +This way, the credentials will be available in all of the subcharts. + +## Persistence + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image stores the PostgreSQL data and configurations at the `/bitnami/postgresql` path of the container. + +Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. +See the [Parameters](#parameters) section to configure the PVC or to disable persistence. + +If you already have data in it, you will fail to sync to standby nodes for all commits, details can refer to [code](https://github.com/bitnami/bitnami-docker-postgresql/blob/8725fe1d7d30ebe8d9a16e9175d05f7ad9260c93/9.6/debian-9/rootfs/libpostgresql.sh#L518-L556). If you need to use those data, please covert them to sql and import after `helm install` finished. + +## NetworkPolicy + +To enable network policy for PostgreSQL, install [a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), and set `networkPolicy.enabled` to `true`. + +For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: + +```console +$ kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" +``` + +With NetworkPolicy enabled, traffic will be limited to just port 5432. + +For more precise policy, set `networkPolicy.allowExternal=false`. This will only allow pods with the generated client label to connect to PostgreSQL. +This label will be displayed in the output of a successful install. + +## Differences between Bitnami PostgreSQL image and [Docker Official](https://hub.docker.com/_/postgres) image + +- The Docker Official PostgreSQL image does not support replication. If you pass any replication environment variable, this would be ignored. The only environment variables supported by the Docker Official image are POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD, POSTGRES_INITDB_ARGS, POSTGRES_INITDB_WALDIR and PGDATA. All the remaining environment variables are specific to the Bitnami PostgreSQL image. +- The Bitnami PostgreSQL image is non-root by default. This requires that you run the pod with `securityContext` and updates the permissions of the volume with an `initContainer`. A key benefit of this configuration is that the pod follows security best practices and is prepared to run on Kubernetes distributions with hard security constraints like OpenShift. +- For OpenShift, one may either define the runAsUser and fsGroup accordingly, or try this more dynamic option: volumePermissions.securityContext.runAsUser="auto",securityContext.enabled=false,containerSecurityContext.enabled=false,shmVolume.chmod.enabled=false + +### Deploy chart using Docker Official PostgreSQL Image + +From chart version 4.0.0, it is possible to use this chart with the Docker Official PostgreSQL image. +Besides specifying the new Docker repository and tag, it is important to modify the PostgreSQL data directory and volume mount point. Basically, the PostgreSQL data dir cannot be the mount point directly, it has to be a subdirectory. + +``` +image.repository=postgres +image.tag=10.6 +postgresqlDataDir=/data/pgdata +persistence.mountPath=/data/ +``` + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` paremeter(s). Find more infomation about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/master/bitnami/common#affinities) chart. To do so, set the `XXX.podAffinityPreset`, `XXX.podAntiAffinityPreset`, or `XXX.nodeAffinityPreset` parameters. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami’s Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +It's necessary to specify the existing passwords while performing an upgrade to ensure the secrets are not updated with invalid randomly generated passwords. Remember to specify the existing values of the `postgresqlPassword` and `replication.password` parameters when upgrading the chart: + +```bash +$ helm upgrade my-release bitnami/postgresql \ + --set postgresqlPassword=[POSTGRESQL_PASSWORD] \ + --set replication.password=[REPLICATION_PASSWORD] +``` + +> Note: you need to substitute the placeholders _[POSTGRESQL_PASSWORD]_, and _[REPLICATION_PASSWORD]_ with the values obtained from instructions in the installation notes. + +### To 10.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Move dependency information from the *requirements.yaml* to the *Chart.yaml* +- After running `helm dependency update`, a *Chart.lock* file is generated containing the same structure used in the previous *requirements.lock* +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Chart. + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +#### Breaking changes + +- The term `master` has been replaced with `primary` and `slave` with `readReplicas` throughout the chart. Role names have changed from `master` and `slave` to `primary` and `read`. + +To upgrade to `10.0.0`, it should be done reusing the PVCs used to hold the PostgreSQL data on your previous release. To do so, follow the instructions below (the following example assumes that the release name is `postgresql`): + +> NOTE: Please, create a backup of your database before running any of those actions. + +Obtain the credentials and the names of the PVCs used to hold the PostgreSQL data on your current release: + +```console +$ export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +$ export POSTGRESQL_PVC=$(kubectl get pvc -l app.kubernetes.io/instance=postgresql,role=master -o jsonpath="{.items[0].metadata.name}") +``` + +Delete the PostgreSQL statefulset. Notice the option `--cascade=false`: + +```console +$ kubectl delete statefulsets.apps postgresql-postgresql --cascade=false +``` + +Now the upgrade works: + +```console +$ helm upgrade postgresql bitnami/postgresql --set postgresqlPassword=$POSTGRESQL_PASSWORD --set persistence.existingClaim=$POSTGRESQL_PVC +``` + +You will have to delete the existing PostgreSQL pod and the new statefulset is going to create a new one + +```console +$ kubectl delete pod postgresql-postgresql-0 +``` + +Finally, you should see the lines below in PostgreSQL container logs: + +```console +$ kubectl logs $(kubectl get pods -l app.kubernetes.io/instance=postgresql,app.kubernetes.io/name=postgresql,role=primary -o jsonpath="{.items[0].metadata.name}") +... +postgresql 08:05:12.59 INFO ==> Deploying PostgreSQL with persisted data... +... +``` + +### To 9.0.0 + +In this version the chart was adapted to follow the Helm label best practices, see [PR 3021](https://github.com/bitnami/charts/pull/3021). That means the backward compatibility is not guarantee when upgrading the chart to this major version. + +As a workaround, you can delete the existing statefulset (using the `--cascade=false` flag pods are not deleted) before upgrade the chart. For example, this can be a valid workflow: + +- Deploy an old version (8.X.X) + +```console +$ helm install postgresql bitnami/postgresql --version 8.10.14 +``` + +- Old version is up and running + +```console +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 1 2020-08-04 13:39:54.783480286 +0000 UTC deployed postgresql-8.10.14 11.8.0 + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 76s +``` + +- The upgrade to the latest one (9.X.X) is going to fail + +```console +$ helm upgrade postgresql bitnami/postgresql +Error: UPGRADE FAILED: cannot patch "postgresql-postgresql" with kind StatefulSet: StatefulSet.apps "postgresql-postgresql" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden +``` + +- Delete the statefulset + +```console +$ kubectl delete statefulsets.apps --cascade=false postgresql-postgresql +statefulset.apps "postgresql-postgresql" deleted +``` + +- Now the upgrade works + +```console +$ helm upgrade postgresql bitnami/postgresql +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 3 2020-08-04 13:42:08.020385884 +0000 UTC deployed postgresql-9.1.2 11.8.0 +``` + +- We can kill the existing pod and the new statefulset is going to create a new one: + +```console +$ kubectl delete pod postgresql-postgresql-0 +pod "postgresql-postgresql-0" deleted + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 19s +``` + +Please, note that without the `--cascade=false` both objects (statefulset and pod) are going to be removed and both objects will be deployed again with the `helm upgrade` command + +### To 8.0.0 + +Prefixes the port names with their protocols to comply with Istio conventions. + +If you depend on the port names in your setup, make sure to update them to reflect this change. + +### To 7.1.0 + +Adds support for LDAP configuration. + +### To 7.0.0 + +Helm performs a lookup for the object based on its group (apps), version (v1), and kind (Deployment). Also known as its GroupVersionKind, or GVK. Changing the GVK is considered a compatibility breaker from Kubernetes' point of view, so you cannot "upgrade" those objects to the new GVK in-place. Earlier versions of Helm 3 did not perform the lookup correctly which has since been fixed to match the spec. + +In https://github.com/helm/charts/pull/17281 the `apiVersion` of the statefulset resources was updated to `apps/v1` in tune with the api's deprecated, resulting in compatibility breakage. + +This major version bump signifies this change. + +### To 6.5.7 + +In this version, the chart will use PostgreSQL with the Postgis extension included. The version used with Postgresql version 10, 11 and 12 is Postgis 2.5. It has been compiled with the following dependencies: + +- protobuf +- protobuf-c +- json-c +- geos +- proj + +### To 5.0.0 + +In this version, the **chart is using PostgreSQL 11 instead of PostgreSQL 10**. You can find the main difference and notable changes in the following links: [https://www.postgresql.org/about/news/1894/](https://www.postgresql.org/about/news/1894/) and [https://www.postgresql.org/about/featurematrix/](https://www.postgresql.org/about/featurematrix/). + +For major releases of PostgreSQL, the internal data storage format is subject to change, thus complicating upgrades, you can see some errors like the following one in the logs: + +```console +Welcome to the Bitnami postgresql container +Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-postgresql +Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-postgresql/issues +Send us your feedback at containers@bitnami.com + +INFO ==> ** Starting PostgreSQL setup ** +NFO ==> Validating settings in POSTGRESQL_* env vars.. +INFO ==> Initializing PostgreSQL database... +INFO ==> postgresql.conf file not detected. Generating it... +INFO ==> pg_hba.conf file not detected. Generating it... +INFO ==> Deploying PostgreSQL with persisted data... +INFO ==> Configuring replication parameters +INFO ==> Loading custom scripts... +INFO ==> Enabling remote connections +INFO ==> Stopping PostgreSQL... +INFO ==> ** PostgreSQL setup finished! ** + +INFO ==> ** Starting PostgreSQL ** + [1] FATAL: database files are incompatible with server + [1] DETAIL: The data directory was initialized by PostgreSQL version 10, which is not compatible with this version 11.3. +``` + +In this case, you should migrate the data from the old chart to the new one following an approach similar to that described in [this section](https://www.postgresql.org/docs/current/upgrading.html#UPGRADING-VIA-PGDUMPALL) from the official documentation. Basically, create a database dump in the old chart, move and restore it in the new one. + +### To 4.0.0 + +This chart will use by default the Bitnami PostgreSQL container starting from version `10.7.0-r68`. This version moves the initialization logic from node.js to bash. This new version of the chart requires setting the `POSTGRES_PASSWORD` in the slaves as well, in order to properly configure the `pg_hba.conf` file. Users from previous versions of the chart are advised to upgrade immediately. + +IMPORTANT: If you do not want to upgrade the chart version then make sure you use the `10.7.0-r68` version of the container. Otherwise, you will get this error + +``` +The POSTGRESQL_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development +``` + +### To 3.0.0 + +This releases make it possible to specify different nodeSelector, affinity and tolerations for master and slave pods. +It also fixes an issue with `postgresql.master.fullname` helper template not obeying fullnameOverride. + +#### Breaking changes + +- `affinty` has been renamed to `master.affinity` and `slave.affinity`. +- `tolerations` has been renamed to `master.tolerations` and `slave.tolerations`. +- `nodeSelector` has been renamed to `master.nodeSelector` and `slave.nodeSelector`. + +### To 2.0.0 + +In order to upgrade from the `0.X.X` branch to `1.X.X`, you should follow the below steps: + +- Obtain the service name (`SERVICE_NAME`) and password (`OLD_PASSWORD`) of the existing postgresql chart. You can find the instructions to obtain the password in the NOTES.txt, the service name can be obtained by running + +```console +$ kubectl get svc +``` + +- Install (not upgrade) the new version + +```console +$ helm repo update +$ helm install my-release bitnami/postgresql +``` + +- Connect to the new pod (you can obtain the name by running `kubectl get pods`): + +```console +$ kubectl exec -it NAME bash +``` + +- Once logged in, create a dump file from the previous database using `pg_dump`, for that we should connect to the previous postgresql chart: + +```console +$ pg_dump -h SERVICE_NAME -U postgres DATABASE_NAME > /tmp/backup.sql +``` + +After run above command you should be prompted for a password, this password is the previous chart password (`OLD_PASSWORD`). +This operation could take some time depending on the database size. + +- Once you have the backup file, you can restore it with a command like the one below: + +```console +$ psql -U postgres DATABASE_NAME < /tmp/backup.sql +``` + +In this case, you are accessing to the local postgresql, so the password should be the new one (you can find it in NOTES.txt). + +If you want to restore the database and the database schema does not exist, it is necessary to first follow the steps described below. + +```console +$ psql -U postgres +postgres=# drop database DATABASE_NAME; +postgres=# create database DATABASE_NAME; +postgres=# create user USER_NAME; +postgres=# alter role USER_NAME with password 'BITNAMI_USER_PASSWORD'; +postgres=# grant all privileges on database DATABASE_NAME to USER_NAME; +postgres=# alter database DATABASE_NAME owner to USER_NAME; +``` diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/.helmignore b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/.helmignore new file mode 100644 index 0000000000..50af031725 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/Chart.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/Chart.yaml new file mode 100644 index 0000000000..bcc3808d08 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.4.2 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/master/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: common +sources: +- https://github.com/bitnami/charts +- http://www.bitnami.com/ +type: library +version: 1.4.2 diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/README.md b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/README.md new file mode 100644 index 0000000000..7287cbb5fc --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/README.md @@ -0,0 +1,322 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.1.0 + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|-------------------------------|------------------------------------------------------|------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.node.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pod.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pod.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|----------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|--------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Return the proper Docker Image Registry Secret Names | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Inpput | +|-------------------------|------------------------------------------------------------|-------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|---------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | +| `common.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|---------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "subchart" "subchart" "context" $` secret, field and subchart are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for RedisTM are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB® are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|------------------------------|----------------------------------|------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets. + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 --decode) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 --decode) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_affinities.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_affinities.tpl new file mode 100644 index 0000000000..493a6dc7e4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_affinities.tpl @@ -0,0 +1,94 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_capabilities.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100644 index 0000000000..4dde56a38d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_capabilities.tpl @@ -0,0 +1,95 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_errors.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100644 index 0000000000..a79cc2e322 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: You must provide your current passwords when upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_images.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_images.tpl new file mode 100644 index 0000000000..60f04fd6e2 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_images.tpl @@ -0,0 +1,47 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_ingress.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_ingress.tpl new file mode 100644 index 0000000000..622ef50e3c --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_ingress.tpl @@ -0,0 +1,42 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if typeIs "int" .servicePort }} + number: {{ .servicePort }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_labels.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100644 index 0000000000..252066c7e2 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_names.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_names.tpl new file mode 100644 index 0000000000..adf2a74f48 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_names.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | 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 "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_secrets.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100644 index 0000000000..60b84a7019 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_secrets.tpl @@ -0,0 +1,129 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- if index $secret.data .key }} + {{- $password = index $secret.data .key }} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_storage.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100644 index 0000000000..60e2a844f6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_tplvalues.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000000..2db166851b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_utils.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100644 index 0000000000..ea083a249f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ .context.Release.Namespace | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 --decode) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_warnings.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000000..ae10fa41ee --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_cassandra.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000000..8679ddffb1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mariadb.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000000..bb5ed7253d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mongodb.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000000..7d5ecbccb4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB(R) required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB(R) values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB(R) is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (not $existingSecret) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB(R) is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_postgresql.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000000..992bcd3908 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,131 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_redis.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000000..3e2a47c039 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,72 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis(TM) required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $existingSecret := include "common.redis.values.existingSecret" . -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $valueKeyRedisPassword := printf "%s%s" $valueKeyPrefix "password" -}} + {{- $valueKeyRedisUsePassword := printf "%s%s" $valueKeyPrefix "usePassword" -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $usePassword := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUsePassword "context" .context) -}} + {{- if eq $usePassword "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Redis Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.redis.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Redis(TM) is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.redis.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_validations.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000000..9a814cf40d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "subchart" "subchart" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s%s=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/values.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/values.yaml new file mode 100644 index 0000000000..9ecdc93f58 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/charts/common/values.yaml @@ -0,0 +1,3 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +exampleValue: common-chart diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/commonAnnotations.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/commonAnnotations.yaml new file mode 100644 index 0000000000..97e18a4cc0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/commonAnnotations.yaml @@ -0,0 +1,3 @@ +commonAnnotations: + helm.sh/hook: "\"pre-install, pre-upgrade\"" + helm.sh/hook-weight: "-1" diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/default-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/default-values.yaml new file mode 100644 index 0000000000..fc2ba605ad --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/default-values.yaml @@ -0,0 +1 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/shmvolume-disabled-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/shmvolume-disabled-values.yaml new file mode 100644 index 0000000000..347d3b40a8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/ci/shmvolume-disabled-values.yaml @@ -0,0 +1,2 @@ +shmVolume: + enabled: false diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/README.md b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/README.md new file mode 100644 index 0000000000..1813a2feaa --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/README.md @@ -0,0 +1 @@ +Copy here your postgresql.conf and/or pg_hba.conf files to use it as a config map. diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/conf.d/README.md b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/conf.d/README.md new file mode 100644 index 0000000000..184c1875d5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/conf.d/README.md @@ -0,0 +1,4 @@ +If you don't want to provide the whole configuration file and only specify certain parameters, you can copy here your extended `.conf` files. +These files will be injected as a config maps and add/overwrite the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +More info in the [bitnami-docker-postgresql README](https://github.com/bitnami/bitnami-docker-postgresql#configuration-file). diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/docker-entrypoint-initdb.d/README.md b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/docker-entrypoint-initdb.d/README.md new file mode 100644 index 0000000000..cba38091e0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/files/docker-entrypoint-initdb.d/README.md @@ -0,0 +1,3 @@ +You can copy here your custom `.sh`, `.sql` or `.sql.gz` file so they are executed during the first boot of the image. + +More info in the [bitnami-docker-postgresql](https://github.com/bitnami/bitnami-docker-postgresql#initializing-a-new-instance) repository. \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/NOTES.txt b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/NOTES.txt new file mode 100644 index 0000000000..4e98958c13 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/NOTES.txt @@ -0,0 +1,59 @@ +** Please be patient while the chart is being deployed ** + +PostgreSQL can be accessed via port {{ template "postgresql.port" . }} on the following DNS name from within your cluster: + + {{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local - Read/Write connection +{{- if .Values.replication.enabled }} + {{ template "common.names.fullname" . }}-read.{{ .Release.Namespace }}.svc.cluster.local - Read only connection +{{- end }} + +{{- if not (eq (include "postgresql.username" .) "postgres") }} + +To get the password for "postgres" run: + + export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-postgres-password}" | base64 --decode) +{{- end }} + +To get the password for "{{ template "postgresql.username" . }}" run: + + export POSTGRES_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-password}" | base64 --decode) + +To connect to your database run the following command: + + kubectl run {{ template "common.names.fullname" . }}-client --rm --tty -i --restart='Never' --namespace {{ .Release.Namespace }} --image {{ template "postgresql.image" . }} --env="PGPASSWORD=$POSTGRES_PASSWORD" {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} + --labels="{{ template "common.names.fullname" . }}-client=true" {{- end }} --command -- psql --host {{ template "common.names.fullname" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.port" . }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label {{ template "common.names.fullname" . }}-client=true" will be able to connect to this PostgreSQL cluster. +{{- end }} + +To connect to your database from outside the cluster execute the following commands: + +{{- if contains "NodePort" .Values.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "common.names.fullname" . }}) + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $NODE_IP --port $NODE_PORT -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} + +{{- else if contains "LoadBalancer" .Values.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $SERVICE_IP --port {{ template "postgresql.port" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} + +{{- else if contains "ClusterIP" .Values.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "common.names.fullname" . }} {{ template "postgresql.port" . }}:{{ template "postgresql.port" . }} & + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host 127.0.0.1 -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.port" . }} + +{{- end }} + +{{- include "postgresql.validateValues" . -}} + +{{- include "common.warnings.rollingTag" .Values.image -}} + +{{- $passwordValidationErrors := include "common.validations.values.postgresql.passwords" (dict "secret" (include "common.names.fullname" .) "context" $) -}} + +{{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $passwordValidationErrors) "context" $) -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/_helpers.tpl b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 0000000000..1f98efe789 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/_helpers.tpl @@ -0,0 +1,337 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "postgresql.name" -}} +{{- default .Chart.Name .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). +*/}} +{{- define "postgresql.primary.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- $fullname := default (printf "%s-%s" .Release.Name $name) .Values.fullnameOverride -}} +{{- if .Values.replication.enabled -}} +{{- printf "%s-%s" $fullname "primary" | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $fullname | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper PostgreSQL image name +*/}} +{{- define "postgresql.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper PostgreSQL metrics image name +*/}} +{{- define "postgresql.metrics.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "postgresql.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "postgresql.imagePullSecrets" -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image .Values.volumePermissions.image) "global" .Values.global) }} +{{- end -}} + +{{/* +Return PostgreSQL postgres user password +*/}} +{{- define "postgresql.postgres.password" -}} +{{- if .Values.global.postgresql.postgresqlPostgresPassword }} + {{- .Values.global.postgresql.postgresqlPostgresPassword -}} +{{- else if .Values.postgresqlPostgresPassword -}} + {{- .Values.postgresqlPostgresPassword -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL password +*/}} +{{- define "postgresql.password" -}} +{{- if .Values.global.postgresql.postgresqlPassword }} + {{- .Values.global.postgresql.postgresqlPassword -}} +{{- else if .Values.postgresqlPassword -}} + {{- .Values.postgresqlPassword -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication password +*/}} +{{- define "postgresql.replication.password" -}} +{{- if .Values.global.postgresql.replicationPassword }} + {{- .Values.global.postgresql.replicationPassword -}} +{{- else if .Values.replication.password -}} + {{- .Values.replication.password -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL username +*/}} +{{- define "postgresql.username" -}} +{{- if .Values.global.postgresql.postgresqlUsername }} + {{- .Values.global.postgresql.postgresqlUsername -}} +{{- else -}} + {{- .Values.postgresqlUsername -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication username +*/}} +{{- define "postgresql.replication.username" -}} +{{- if .Values.global.postgresql.replicationUser }} + {{- .Values.global.postgresql.replicationUser -}} +{{- else -}} + {{- .Values.replication.user -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL port +*/}} +{{- define "postgresql.port" -}} +{{- if .Values.global.postgresql.servicePort }} + {{- .Values.global.postgresql.servicePort -}} +{{- else -}} + {{- .Values.service.port -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL created database +*/}} +{{- define "postgresql.database" -}} +{{- if .Values.global.postgresql.postgresqlDatabase }} + {{- .Values.global.postgresql.postgresqlDatabase -}} +{{- else if .Values.postgresqlDatabase -}} + {{- .Values.postgresqlDatabase -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "postgresql.secretName" -}} +{{- if .Values.global.postgresql.existingSecret }} + {{- printf "%s" (tpl .Values.global.postgresql.existingSecret $) -}} +{{- else if .Values.existingSecret -}} + {{- printf "%s" (tpl .Values.existingSecret $) -}} +{{- else -}} + {{- printf "%s" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if we should use an existingSecret. +*/}} +{{- define "postgresql.useExistingSecret" -}} +{{- if or .Values.global.postgresql.existingSecret .Values.existingSecret -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created +*/}} +{{- define "postgresql.createSecret" -}} +{{- if not (include "postgresql.useExistingSecret" .) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the configuration ConfigMap name. +*/}} +{{- define "postgresql.configurationCM" -}} +{{- if .Values.configurationConfigMap -}} +{{- printf "%s" (tpl .Values.configurationConfigMap $) -}} +{{- else -}} +{{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the extended configuration ConfigMap name. +*/}} +{{- define "postgresql.extendedConfigurationCM" -}} +{{- if .Values.extendedConfConfigMap -}} +{{- printf "%s" (tpl .Values.extendedConfConfigMap $) -}} +{{- else -}} +{{- printf "%s-extended-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap should be mounted with PostgreSQL configuration +*/}} +{{- define "postgresql.mountConfigurationCM" -}} +{{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts ConfigMap name. +*/}} +{{- define "postgresql.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} +{{- printf "%s" (tpl .Values.initdbScriptsConfigMap $) -}} +{{- else -}} +{{- printf "%s-init-scripts" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts Secret name. +*/}} +{{- define "postgresql.initdbScriptsSecret" -}} +{{- printf "%s" (tpl .Values.initdbScriptsSecret $) -}} +{{- end -}} + +{{/* +Get the metrics ConfigMap name. +*/}} +{{- define "postgresql.metricsCM" -}} +{{- printf "%s-metrics" (include "common.names.fullname" .) -}} +{{- end -}} + +{{/* +Get the readiness probe command +*/}} +{{- define "postgresql.readinessProbeCommand" -}} +- | +{{- if (include "postgresql.database" .) }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} +{{- else }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} +{{- end }} +{{- if contains "bitnami/" .Values.image.repository }} + [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "postgresql.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "postgresql.validateValues.ldapConfigurationMethod" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.psp" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.tls" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If ldap.url is used then you don't need the other settings for ldap +*/}} +{{- define "postgresql.validateValues.ldapConfigurationMethod" -}} +{{- if and .Values.ldap.enabled (and (not (empty .Values.ldap.url)) (not (empty .Values.ldap.server))) }} +postgresql: ldap.url, ldap.server + You cannot set both `ldap.url` and `ldap.server` at the same time. + Please provide a unique way to configure LDAP. + More info at https://www.postgresql.org/docs/current/auth-ldap.html +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If PSP is enabled RBAC should be enabled too +*/}} +{{- define "postgresql.validateValues.psp" -}} +{{- if and .Values.psp.create (not .Values.rbac.create) }} +postgresql: psp.create, rbac.create + RBAC should be enabled if PSP is enabled in order for PSP to work. + More info at https://kubernetes.io/docs/concepts/policy/pod-security-policy/#authorizing-policies +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for podsecuritypolicy. +*/}} +{{- define "podsecuritypolicy.apiVersion" -}} +{{- if semverCompare "<1.10-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "policy/v1beta1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "postgresql.networkPolicy.apiVersion" -}} +{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}} +"extensions/v1beta1" +{{- else if semverCompare "^1.7-0" .Capabilities.KubeVersion.GitVersion -}} +"networking.k8s.io/v1" +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql TLS - When TLS is enabled, so must be VolumePermissions +*/}} +{{- define "postgresql.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.volumePermissions.enabled) }} +postgresql: tls.enabled, volumePermissions.enabled + When TLS is enabled you must enable volumePermissions as well to ensure certificates files have + the right permissions. +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "postgresql.tlsCert" -}} +{{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "postgresql.tlsCertKey" -}} +{{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "postgresql.tlsCACert" -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.certCAFilename -}} +{{- end -}} + +{{/* +Return the path to the CRL file. +*/}} +{{- define "postgresql.tlsCRL" -}} +{{- if .Values.tls.crlFilename -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.crlFilename -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/configmap.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/configmap.yaml new file mode 100644 index 0000000000..3a5ea18ae9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/configmap.yaml @@ -0,0 +1,31 @@ +{{ if and (or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration) (not .Values.configurationConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-configuration + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: +{{- if (.Files.Glob "files/postgresql.conf") }} +{{ (.Files.Glob "files/postgresql.conf").AsConfig | indent 2 }} +{{- else if .Values.postgresqlConfiguration }} + postgresql.conf: | +{{- range $key, $value := default dict .Values.postgresqlConfiguration }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- if (.Files.Glob "files/pg_hba.conf") }} +{{ (.Files.Glob "files/pg_hba.conf").AsConfig | indent 2 }} +{{- else if .Values.pgHbaConfiguration }} + pg_hba.conf: | +{{ .Values.pgHbaConfiguration | indent 4 }} +{{- end }} +{{ end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extended-config-configmap.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extended-config-configmap.yaml new file mode 100644 index 0000000000..b0dad253b5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extended-config-configmap.yaml @@ -0,0 +1,26 @@ +{{- if and (or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf) (not .Values.extendedConfConfigMap)}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-extended-configuration + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: +{{- with .Files.Glob "files/conf.d/*.conf" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{ with .Values.postgresqlExtendedConf }} + override.conf: | +{{- range $key, $value := . }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extra-list.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extra-list.yaml new file mode 100644 index 0000000000..9ac65f9e16 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/initialization-configmap.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/initialization-configmap.yaml new file mode 100644 index 0000000000..7796c67a93 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/initialization-configmap.yaml @@ -0,0 +1,25 @@ +{{- if and (or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScripts) (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-init-scripts + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.sql.gz" }} +binaryData: +{{- range $path, $bytes := . }} + {{ base $path }}: {{ $.Files.Get $path | b64enc | quote }} +{{- end }} +{{- end }} +data: +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql}" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{- with .Values.initdbScripts }} +{{ toYaml . | indent 2 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-configmap.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-configmap.yaml new file mode 100644 index 0000000000..fa539582bb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "postgresql.metricsCM" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: + custom-metrics.yaml: {{ toYaml .Values.metrics.customMetrics | quote }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-svc.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-svc.yaml new file mode 100644 index 0000000000..af8b67e2ff --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/metrics-svc.yaml @@ -0,0 +1,26 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-metrics + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- toYaml .Values.metrics.service.annotations | nindent 4 }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ .Values.metrics.service.type }} + {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{- end }} + ports: + - name: http-metrics + port: 9187 + targetPort: http-metrics + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/networkpolicy.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/networkpolicy.yaml new file mode 100644 index 0000000000..4f2740ea0c --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/networkpolicy.yaml @@ -0,0 +1,39 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "postgresql.networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + ingress: + # Allow inbound connections + - ports: + - port: {{ template "postgresql.port" . }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.explicitNamespacesSelector }} + namespaceSelector: +{{ toYaml .Values.networkPolicy.explicitNamespacesSelector | indent 12 }} + {{- end }} + - podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 14 }} + role: read + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes + - ports: + - port: 9187 + {{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/podsecuritypolicy.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000000..0c49694fad --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/podsecuritypolicy.yaml @@ -0,0 +1,38 @@ +{{- if .Values.psp.create }} +apiVersion: {{ include "podsecuritypolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + privileged: false + volumes: + - 'configMap' + - 'secret' + - 'persistentVolumeClaim' + - 'emptyDir' + - 'projected' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/prometheusrule.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/prometheusrule.yaml new file mode 100644 index 0000000000..d0f408c78f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/prometheusrule.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} +{{- with .Values.metrics.prometheusRule.namespace }} + namespace: {{ . }} +{{- end }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- with .Values.metrics.prometheusRule.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: +{{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "postgresql.name" $ }} + rules: {{ tpl (toYaml .) $ | nindent 8 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/role.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/role.yaml new file mode 100644 index 0000000000..017a5716b1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/role.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +kind: Role +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +rules: + {{- if .Values.psp.create }} + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + verbs: ["use"] + resourceNames: + - {{ template "common.names.fullname" . }} + {{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/rolebinding.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/rolebinding.yaml new file mode 100644 index 0000000000..189775a15a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/rolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ template "common.names.fullname" . }} + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/secrets.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/secrets.yaml new file mode 100644 index 0000000000..d492cd593b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/secrets.yaml @@ -0,0 +1,24 @@ +{{- if (include "postgresql.createSecret" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + {{- if not (eq (include "postgresql.username" .) "postgres") }} + postgresql-postgres-password: {{ include "postgresql.postgres.password" . | b64enc | quote }} + {{- end }} + postgresql-password: {{ include "postgresql.password" . | b64enc | quote }} + {{- if .Values.replication.enabled }} + postgresql-replication-password: {{ include "postgresql.replication.password" . | b64enc | quote }} + {{- end }} + {{- if (and .Values.ldap.enabled .Values.ldap.bind_password)}} + postgresql-ldap-password: {{ .Values.ldap.bind_password | b64enc | quote }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/serviceaccount.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/serviceaccount.yaml new file mode 100644 index 0000000000..03f0f50e7d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if and (.Values.serviceAccount.enabled) (not .Values.serviceAccount.name) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "common.labels.standard" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/servicemonitor.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/servicemonitor.yaml new file mode 100644 index 0000000000..587ce85b87 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/servicemonitor.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "common.names.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- end }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.metrics.serviceMonitor.additionalLabels }} + {{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + +spec: + endpoints: + - port: http-metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset-readreplicas.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset-readreplicas.yaml new file mode 100644 index 0000000000..b038299bf6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset-readreplicas.yaml @@ -0,0 +1,411 @@ +{{- if .Values.replication.enabled }} +{{- $readReplicasResources := coalesce .Values.readReplicas.resources .Values.resources -}} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: "{{ template "common.names.fullname" . }}-read" + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: read +{{- with .Values.readReplicas.labels }} +{{ toYaml . | indent 4 }} +{{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.readReplicas.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: {{ .Values.replication.readReplicas }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: read + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: read + role: read +{{- with .Values.readReplicas.podLabels }} +{{ toYaml . | indent 8 }} +{{- end }} +{{- with .Values.readReplicas.podAnnotations }} + annotations: +{{ toYaml . | indent 8 }} +{{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.readReplicas.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAffinityPreset "component" "read" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAntiAffinityPreset "component" "read" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.readReplicas.nodeAffinityPreset.type "key" .Values.readReplicas.nodeAffinityPreset.key "values" .Values.readReplicas.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.readReplicas.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name}} + {{- end }} + {{- if or .Values.readReplicas.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + {{ if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.readReplicas.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.readReplicas.priorityClassName }} + priorityClassName: {{ .Values.readReplicas.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if $readReplicasResources }} + resources: {{- toYaml $readReplicasResources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + - name: POSTGRESQL_PORT_NUMBER + value: "{{ template "postgresql.port" . }}" + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + - name: POSTGRES_REPLICATION_MODE + value: "slave" + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + - name: POSTGRES_MASTER_HOST + value: {{ template "common.names.fullname" . }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ include "postgresql.port" . | quote }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ template "postgresql.port" . }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{ end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.readReplicas.extraVolumeMounts }} + {{- toYaml .Values.readReplicas.extraVolumeMounts | nindent 12 }} + {{- end }} +{{- if .Values.readReplicas.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.sidecars "context" $ ) | nindent 8 }} +{{- end }} + volumes: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi + {{- end }} + {{- if or (not .Values.persistence.enabled) (not .Values.readReplicas.persistence.enabled) }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.readReplicas.extraVolumes }} + {{- toYaml .Values.readReplicas.extraVolumes | nindent 8 }} + {{- end }} + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} +{{- if and .Values.persistence.enabled .Values.readReplicas.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 0000000000..f8163fd99f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/statefulset.yaml @@ -0,0 +1,609 @@ +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ template "postgresql.primary.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- with .Values.primary.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.primary.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: 1 + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: primary + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + role: primary + app.kubernetes.io/component: primary + {{- with .Values.primary.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.primary.podAnnotations }} + annotations: {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.primary.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.primary.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAffinityPreset "component" "primary" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAntiAffinityPreset "component" "primary" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.primary.nodeAffinityPreset.type "key" .Values.primary.nodeAffinityPreset.key "values" .Values.primary.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.primary.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.primary.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.primary.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.primary.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + {{- end }} + {{- if or .Values.primary.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.primary.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.primary.priorityClassName }} + priorityClassName: {{ .Values.primary.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: POSTGRESQL_PORT_NUMBER + value: "{{ template "postgresql.port" . }}" + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + {{- if .Values.postgresqlInitdbArgs }} + - name: POSTGRES_INITDB_ARGS + value: {{ .Values.postgresqlInitdbArgs | quote }} + {{- end }} + {{- if .Values.postgresqlInitdbWalDir }} + - name: POSTGRES_INITDB_WALDIR + value: {{ .Values.postgresqlInitdbWalDir | quote }} + {{- end }} + {{- if .Values.initdbUser }} + - name: POSTGRESQL_INITSCRIPTS_USERNAME + value: {{ .Values.initdbUser }} + {{- end }} + {{- if .Values.initdbPassword }} + - name: POSTGRESQL_INITSCRIPTS_PASSWORD + value: {{ .Values.initdbPassword }} + {{- end }} + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + {{- if .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_MASTER_HOST + value: {{ .Values.primaryAsStandBy.primaryHost }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ .Values.primaryAsStandBy.primaryPort | quote }} + {{- end }} + {{- if or .Values.replication.enabled .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_REPLICATION_MODE + {{- if .Values.primaryAsStandBy.enabled }} + value: "slave" + {{- else }} + value: "master" + {{- end }} + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + {{- if not (eq .Values.replication.synchronousCommit "off")}} + - name: POSTGRES_SYNCHRONOUS_COMMIT_MODE + value: {{ .Values.replication.synchronousCommit | quote }} + - name: POSTGRES_NUM_SYNCHRONOUS_REPLICAS + value: {{ .Values.replication.numSynchronousReplicas | quote }} + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + {{- end }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + - name: POSTGRES_USER + value: {{ include "postgresql.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + {{- if (include "postgresql.database" .) }} + - name: POSTGRES_DB + value: {{ (include "postgresql.database" .) | quote }} + {{- end }} + {{- if .Values.extraEnv }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnv "context" $) | nindent 12 }} + {{- end }} + - name: POSTGRESQL_ENABLE_LDAP + value: {{ ternary "yes" "no" .Values.ldap.enabled | quote }} + {{- if .Values.ldap.enabled }} + - name: POSTGRESQL_LDAP_SERVER + value: {{ .Values.ldap.server }} + - name: POSTGRESQL_LDAP_PORT + value: {{ .Values.ldap.port | quote }} + - name: POSTGRESQL_LDAP_SCHEME + value: {{ .Values.ldap.scheme }} + {{- if .Values.ldap.tls }} + - name: POSTGRESQL_LDAP_TLS + value: "1" + {{- end }} + - name: POSTGRESQL_LDAP_PREFIX + value: {{ .Values.ldap.prefix | quote }} + - name: POSTGRESQL_LDAP_SUFFIX + value: {{ .Values.ldap.suffix | quote }} + - name: POSTGRESQL_LDAP_BASE_DN + value: {{ .Values.ldap.baseDN }} + - name: POSTGRESQL_LDAP_BIND_DN + value: {{ .Values.ldap.bindDN }} + {{- if (not (empty .Values.ldap.bind_password)) }} + - name: POSTGRESQL_LDAP_BIND_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-ldap-password + {{- end}} + - name: POSTGRESQL_LDAP_SEARCH_ATTR + value: {{ .Values.ldap.search_attr }} + - name: POSTGRESQL_LDAP_SEARCH_FILTER + value: {{ .Values.ldap.search_filter }} + - name: POSTGRESQL_LDAP_URL + value: {{ .Values.ldap.url }} + {{- end}} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + {{- if .Values.extraEnvVarsCM }} + envFrom: + - configMapRef: + name: {{ tpl .Values.extraEnvVarsCM . }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ template "postgresql.port" . }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + {{- else if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d/ + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + mountPath: /docker-entrypoint-initdb.d/secret + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.primary.extraVolumeMounts }} + {{- toYaml .Values.primary.extraVolumeMounts | nindent 12 }} + {{- end }} +{{- if .Values.primary.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.primary.sidecars "context" $ ) | nindent 8 }} +{{- end }} +{{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "postgresql.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.securityContext.enabled }} + securityContext: {{- omit .Values.metrics.securityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + {{- $database := required "In order to enable metrics you need to specify a database (.Values.postgresqlDatabase or .Values.global.postgresql.postgresqlDatabase)" (include "postgresql.database" .) }} + {{- $sslmode := ternary "require" "disable" .Values.tls.enabled }} + {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} + - name: DATA_SOURCE_NAME + value: {{ printf "host=127.0.0.1 port=%d user=%s sslmode=%s sslcert=%s sslkey=%s" (int (include "postgresql.port" .)) (include "postgresql.username" .) $sslmode (include "postgresql.tlsCert" .) (include "postgresql.tlsCertKey" .) }} + {{- else }} + - name: DATA_SOURCE_URI + value: {{ printf "127.0.0.1:%d/%s?sslmode=%s" (int (include "postgresql.port" .)) $database $sslmode }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: DATA_SOURCE_PASS_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: DATA_SOURCE_PASS + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: DATA_SOURCE_USER + value: {{ template "postgresql.username" . }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.readinessProbe.failureThreshold }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.customMetrics }} + - name: custom-metrics + mountPath: /conf + readOnly: true + args: ["--extend.query-path", "/conf/custom-metrics.yaml"] + {{- end }} + ports: + - name: http-metrics + containerPort: 9187 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} +{{- end }} + volumes: + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ template "postgresql.initdbScriptsCM" . }} + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + secret: + secretName: {{ template "postgresql.initdbScriptsSecret" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.primary.extraVolumes }} + {{- toYaml .Values.primary.extraVolumes | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} + - name: custom-metrics + configMap: + name: {{ template "postgresql.metricsCM" . }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi + {{- end }} +{{- if and .Values.persistence.enabled .Values.persistence.existingClaim }} + - name: data + persistentVolumeClaim: +{{- with .Values.persistence.existingClaim }} + claimName: {{ tpl . $ }} +{{- end }} +{{- else if not .Values.persistence.enabled }} + - name: data + emptyDir: {} +{{- else if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-headless.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-headless.yaml new file mode 100644 index 0000000000..6f5f3b9ee4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-headless.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-headless + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + # Use this annotation in addition to the actual publishNotReadyAddresses + # field below because the annotation will stop being respected soon but the + # field is broken in some versions of Kubernetes: + # https://github.com/kubernetes/kubernetes/issues/58662 + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + clusterIP: None + # We want all pods in the StatefulSet to have their addresses published for + # the sake of the other Postgresql pods even before they're ready, since they + # have to be able to talk to each other in order to become ready. + publishNotReadyAddresses: true + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-read.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-read.yaml new file mode 100644 index 0000000000..56195ea1e6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc-read.yaml @@ -0,0 +1,43 @@ +{{- if .Values.replication.enabled }} +{{- $serviceAnnotations := coalesce .Values.readReplicas.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.readReplicas.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.readReplicas.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.readReplicas.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.readReplicas.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.readReplicas.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-read + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: read +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc.yaml new file mode 100644 index 0000000000..a29431b6a4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/templates/svc.yaml @@ -0,0 +1,41 @@ +{{- $serviceAnnotations := coalesce .Values.primary.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.primary.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.primary.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.primary.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.primary.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.primary.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.schema.json b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.schema.json new file mode 100644 index 0000000000..66a2a9dd06 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "postgresqlUsername": { + "type": "string", + "title": "Admin user", + "form": true + }, + "postgresqlPassword": { + "type": "string", + "title": "Password", + "form": true + }, + "persistence": { + "type": "object", + "properties": { + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi" + } + } + }, + "resources": { + "type": "object", + "title": "Required Resources", + "description": "Configure resource requests", + "form": true, + "properties": { + "requests": { + "type": "object", + "properties": { + "memory": { + "type": "string", + "form": true, + "render": "slider", + "title": "Memory Request", + "sliderMin": 10, + "sliderMax": 2048, + "sliderUnit": "Mi" + }, + "cpu": { + "type": "string", + "form": true, + "render": "slider", + "title": "CPU Request", + "sliderMin": 10, + "sliderMax": 2000, + "sliderUnit": "m" + } + } + } + } + }, + "replication": { + "type": "object", + "form": true, + "title": "Replication Details", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable Replication", + "form": true + }, + "readReplicas": { + "type": "integer", + "title": "read Replicas", + "form": true, + "hidden": { + "value": false, + "path": "replication/enabled" + } + } + } + }, + "volumePermissions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable Init Containers", + "description": "Change the owner of the persist volume mountpoint to RunAsUser:fsGroup" + } + } + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Configure metrics exporter", + "form": true + } + } + } + } +} diff --git a/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.yaml b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.yaml new file mode 100644 index 0000000000..82ce092344 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/charts/postgresql/values.yaml @@ -0,0 +1,824 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +global: + postgresql: {} +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Bitnami PostgreSQL image version +## ref: https://hub.docker.com/r/bitnami/postgresql/tags/ +## +image: + registry: docker.io + repository: bitnami/postgresql + tag: 11.11.0-debian-10-r71 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Set to true if you would like to see extra information on logs + ## It turns BASH and/or NAMI debugging in the image + ## + debug: false + +## String to partially override common.names.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override common.names.fullname template +## +# fullnameOverride: + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: "10" + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + ## Init container Security Context + ## Note: the chown of the data folder is done to securityContext.runAsUser + ## and not the below volumePermissions.securityContext.runAsUser + ## When runAsUser is set to special value "auto", init container will try to chwon the + ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). + ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with + ## pod securityContext.enabled=false and shmVolume.chmod.enabled=false + ## + securityContext: + runAsUser: 0 + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## Pod Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## +securityContext: + enabled: true + fsGroup: 1001 + +## Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + +## Pod Service Account +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + enabled: false + ## Name of an already existing service account. Setting this value disables the automatic service account creation. + # name: + +## Pod Security Policy +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +psp: + create: false + +## Creates role for ServiceAccount +## Required for PSP +## +rbac: + create: false + +replication: + enabled: false + user: repl_user + password: repl_password + readReplicas: 1 + ## Set synchronous commit mode: on, off, remote_apply, remote_write and local + ## ref: https://www.postgresql.org/docs/9.6/runtime-config-wal.html#GUC-WAL-LEVEL + synchronousCommit: 'off' + ## From the number of `readReplicas` defined above, set the number of those that will have synchronous replication + ## NOTE: It cannot be > readReplicas + numSynchronousReplicas: 0 + ## Replication Cluster application name. Useful for defining multiple replication policies + ## + applicationName: my_application + +## PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-user-on-first-run (see note!) +# postgresqlPostgresPassword: + +## PostgreSQL user (has superuser privileges if username is `postgres`) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +postgresqlUsername: postgres + +## PostgreSQL password +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +# postgresqlPassword: + +## PostgreSQL password using existing secret +## existingSecret: secret +## + +## Mount PostgreSQL secret as a file instead of passing environment variable +# usePasswordFile: false + +## Create a database +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-on-first-run +## +# postgresqlDatabase: + +## PostgreSQL data dir +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +postgresqlDataDir: /bitnami/postgresql/data + +## An array to add extra environment variables +## For example: +## extraEnv: +## - name: FOO +## value: "bar" +## +# extraEnv: +extraEnv: [] + +## Name of a ConfigMap containing extra env vars +## +# extraEnvVarsCM: + +## Specify extra initdb args +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +# postgresqlInitdbArgs: + +## Specify a custom location for the PostgreSQL transaction log +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +# postgresqlInitdbWalDir: + +## PostgreSQL configuration +## Specify runtime configuration parameters as a dict, using camelCase, e.g. +## {"sharedBuffers": "500MB"} +## Alternatively, you can put your postgresql.conf under the files/ directory +## ref: https://www.postgresql.org/docs/current/static/runtime-config.html +## +# postgresqlConfiguration: + +## PostgreSQL extended configuration +## As above, but _appended_ to the main configuration +## Alternatively, you can put your *.conf under the files/conf.d/ directory +## https://github.com/bitnami/bitnami-docker-postgresql#allow-settings-to-be-loaded-from-files-other-than-the-default-postgresqlconf +## +# postgresqlExtendedConf: + +## Configure current cluster's primary server to be the standby server in other cluster. +## This will allow cross cluster replication and provide cross cluster high availability. +## You will need to configure pgHbaConfiguration if you want to enable this feature with local cluster replication enabled. +## +primaryAsStandBy: + enabled: false + # primaryHost: + # primaryPort: + +## PostgreSQL client authentication configuration +## Specify content for pg_hba.conf +## Default: do not create pg_hba.conf +## Alternatively, you can put your pg_hba.conf under the files/ directory +# pgHbaConfiguration: |- +# local all all trust +# host all all localhost trust +# host mydatabase mysuser 192.168.0.0/24 md5 + +## ConfigMap with PostgreSQL configuration +## NOTE: This will override postgresqlConfiguration and pgHbaConfiguration +# configurationConfigMap: + +## ConfigMap with PostgreSQL extended configuration +# extendedConfConfigMap: + +## initdb scripts +## Specify dictionary of scripts to be run at first boot +## Alternatively, you can put your scripts under the files/docker-entrypoint-initdb.d directory +## +# initdbScripts: +# my_init_script.sh: | +# #!/bin/sh +# echo "Do something." + +## ConfigMap with scripts to be run at first boot +## NOTE: This will override initdbScripts +# initdbScriptsConfigMap: + +## Secret with scripts to be run at first boot (in case it contains sensitive information) +## NOTE: This can work along initdbScripts or initdbScriptsConfigMap +# initdbScriptsSecret: + +## Specify the PostgreSQL username and password to execute the initdb scripts +# initdbUser: +# initdbPassword: + +## Audit settings +## https://github.com/bitnami/bitnami-docker-postgresql#auditing +## +audit: + ## Log client hostnames + ## + logHostname: false + ## Log connections to the server + ## + logConnections: false + ## Log disconnections + ## + logDisconnections: false + ## Operation to audit using pgAudit (default if not set) + ## + pgAuditLog: "" + ## Log catalog using pgAudit + ## + pgAuditLogCatalog: "off" + ## Log level for clients + ## + clientMinMessages: error + ## Template for log line prefix (default if not set) + ## + logLinePrefix: "" + ## Log timezone + ## + logTimezone: "" + +## Shared preload libraries +## +postgresqlSharedPreloadLibraries: "pgaudit" + +## Maximum total connections +## +postgresqlMaxConnections: + +## Maximum connections for the postgres user +## +postgresqlPostgresConnectionLimit: + +## Maximum connections for the created user +## +postgresqlDbUserConnectionLimit: + +## TCP keepalives interval +## +postgresqlTcpKeepalivesInterval: + +## TCP keepalives idle +## +postgresqlTcpKeepalivesIdle: + +## TCP keepalives count +## +postgresqlTcpKeepalivesCount: + +## Statement timeout +## +postgresqlStatementTimeout: + +## Remove pg_hba.conf lines with the following comma-separated patterns +## (cannot be used with custom pg_hba.conf) +## +postgresqlPghbaRemoveFilters: + +## Optional duration in seconds the pod needs to terminate gracefully. +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods +## +# terminationGracePeriodSeconds: 30 + +## LDAP configuration +## +ldap: + enabled: false + url: '' + server: '' + port: '' + prefix: '' + suffix: '' + baseDN: '' + bindDN: '' + bind_password: + search_attr: '' + search_filter: '' + scheme: '' + tls: {} + +## PostgreSQL service configuration +## +service: + ## PosgresSQL service type + ## + type: ClusterIP + # clusterIP: None + port: 5432 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. Evaluated as a template. + ## + annotations: {} + ## Set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + # loadBalancerIP: + ## Load Balancer sources. Evaluated as a template. + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## + # loadBalancerSourceRanges: + # - 10.10.10.0/24 + +## Start primary and read(s) pod(s) without limitations on shm memory. +## By default docker and containerd (and possibly other container runtimes) +## limit `/dev/shm` to `64M` (see e.g. the +## [docker issue](https://github.com/docker-library/postgres/issues/416) and the +## [containerd issue](https://github.com/containerd/containerd/issues/3654), +## which could be not enough if PostgreSQL uses parallel workers heavily. +## +shmVolume: + ## Set `shmVolume.enabled` to `true` to mount a new tmpfs volume to remove + ## this limitation. + ## + enabled: true + ## Set to `true` to `chmod 777 /dev/shm` on a initContainer. + ## This option is ignored if `volumePermissions.enabled` is `false` + ## + chmod: + enabled: true + +## PostgreSQL data Persistent Volume Storage Class +## If defined, storageClassName: +## If set to "-", storageClassName: "", which disables dynamic provisioning +## If undefined (the default) or set to null, no storageClassName spec is +## set, choosing the default provisioner. (gp2 on AWS, standard on +## GKE, AWS & OpenStack) +## +persistence: + enabled: true + ## A manually managed Persistent Volume and Claim + ## If defined, PVC must be created manually before volume will be bound + ## The value is evaluated as a template, so, for example, the name can depend on .Release or .Chart + ## + # existingClaim: + + ## The path the volume will be mounted at, useful when using different + ## PostgreSQL images. + ## + mountPath: /bitnami/postgresql + + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + ## + subPath: '' + + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + annotations: {} + ## selector can be used to match an existing PersistentVolume + ## selector: + ## matchLabels: + ## app: my-app + selector: {} + +## updateStrategy for PostgreSQL StatefulSet and its reads StatefulSets +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate + +## +## PostgreSQL Primary parameters +## +primary: + ## PostgreSQL Primary pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## PostgreSQL Primary pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## PostgreSQL Primary node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: primary.podAffinityPreset, primary.podAntiAffinityPreset, and primary.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + + labels: {} + annotations: {} + podLabels: {} + podAnnotations: {} + priorityClassName: '' + ## Extra init containers + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + + ## Additional PostgreSQL primary Volume mounts + ## + extraVolumeMounts: [] + ## Additional PostgreSQL primary Volumes + ## + extraVolumes: [] + ## Add sidecars to the pod + ## + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + + ## Override the service configuration for primary + ## + service: {} + # type: + # nodePort: + # clusterIP: + +## +## PostgreSQL read only replica parameters +## +readReplicas: + ## PostgreSQL read only pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## PostgreSQL read only pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## PostgreSQL read only node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: readReplicas.podAffinityPreset, readReplicas.podAntiAffinityPreset, and readReplicas.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + labels: {} + annotations: {} + podLabels: {} + podAnnotations: {} + priorityClassName: '' + + ## Extra init containers + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + + ## Additional PostgreSQL read replicas Volume mounts + ## + extraVolumeMounts: [] + + ## Additional PostgreSQL read replicas Volumes + ## + extraVolumes: [] + + ## Add sidecars to the pod + ## + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + + ## Override the service configuration for read + ## + service: {} + # type: + # nodePort: + # clusterIP: + + ## Whether to enable PostgreSQL read replicas data Persistent + ## + persistence: + enabled: true + + # Override the resource configuration for read replicas + resources: {} + # requests: + # memory: 256Mi + # cpu: 250m + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + requests: + memory: 256Mi + cpu: 250m + +## Add annotations to all the deployed resources +## +commonAnnotations: {} + +networkPolicy: + ## Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. + ## + enabled: false + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port PostgreSQL is listening + ## on. When true, PostgreSQL will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + + ## if explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace + ## and that match other criteria, the ones that have the good label, can reach the DB. + ## But sometimes, we want the DB to be accessible to clients from other namespaces, in this case, we can use this + ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added. + ## + ## Example: + ## explicitNamespacesSelector: + ## matchLabels: + ## role: frontend + ## matchExpressions: + ## - {key: role, operator: In, values: [frontend]} + ## + explicitNamespacesSelector: {} + +## Configure extra options for startup, liveness and readiness probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 10 + successThreshold: 1 + +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Custom Startup probe +## +customStartupProbe: {} + +## Custom Liveness probe +## +customLivenessProbe: {} + +## Custom Rediness probe +## +customReadinessProbe: {} + +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to use the server's TLS cipher preferences rather than the client's. + preferServerCiphers: true + # + # Name of the Secret that contains the certificates + certificatesSecret: '' + # + # Certificate filename + certFilename: '' + # + # Certificate Key filename + certKeyFilename: '' + # + # CA Certificate filename + # If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate + # ref: https://www.postgresql.org/docs/9.6/auth-methods.html + certCAFilename: + # + # File containing a Certificate Revocation List + crlFilename: + +## Configure metrics exporter +## +metrics: + enabled: false + # resources: {} + service: + type: ClusterIP + annotations: + prometheus.io/scrape: 'true' + prometheus.io/port: '9187' + loadBalancerIP: + serviceMonitor: + enabled: false + additionalLabels: {} + # namespace: monitoring + # interval: 30s + # scrapeTimeout: 10s + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + namespace: '' + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current postgresql service. + ## rules: + ## - alert: HugeReplicationLag + ## expr: pg_replication_lag{service="{{ template "common.names.fullname" . }}-metrics"} / 3600 > 1 + ## for: 1m + ## labels: + ## severity: critical + ## annotations: + ## description: replication for {{ template "common.names.fullname" . }} PostgreSQL is lagging by {{ "{{ $value }}" }} hour(s). + ## summary: PostgreSQL replication is lagging by {{ "{{ $value }}" }} hour(s). + ## + rules: [] + + image: + registry: docker.io + repository: bitnami/postgres-exporter + tag: 0.9.0-debian-10-r43 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + ## Define additional custom metrics + ## ref: https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file + # customMetrics: + # pg_database: + # query: "SELECT d.datname AS name, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_database_size(d.datname) ELSE 0 END AS size_bytes FROM pg_catalog.pg_database d where datname not in ('template0', 'template1', 'postgres')" + # metrics: + # - name: + # usage: "LABEL" + # description: "Name of the database" + # - size_bytes: + # usage: "GAUGE" + # description: "Size of the database in bytes" + # + ## An array to add extra env vars to configure postgres-exporter + ## see: https://github.com/wrouesnel/postgres_exporter#environment-variables + ## For example: + # extraEnvVars: + # - name: PG_EXPORTER_DISABLE_DEFAULT_METRICS + # value: "true" + extraEnvVars: {} + + ## Pod Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## + securityContext: + enabled: false + runAsUser: 1001 + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## Configure extra options for liveness and readiness probes + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Array with extra yaml to deploy with the chart. Evaluated as a template +## +extraDeploy: [] diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/access-tls-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/access-tls-values.yaml new file mode 100644 index 0000000000..27e24d3468 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/access-tls-values.yaml @@ -0,0 +1,34 @@ +databaseUpgradeReady: true +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +access: + accessConfig: + security: + tls: true + resetAccessCAKeys: true diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/default-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/default-values.yaml new file mode 100644 index 0000000000..020f523352 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/default-values.yaml @@ -0,0 +1,32 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true +## This is an exception here because HA needs masterKey to connect with other node members and it is commented in values to support 6.x to 7.x Migration +## Please refer https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/README.md#special-upgrade-notes-1 +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/global-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/global-values.yaml new file mode 100644 index 0000000000..0987e17ca7 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/global-values.yaml @@ -0,0 +1,255 @@ +databaseUpgradeReady: true +artifactory: + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + customInitContainersBegin: | + - name: "custom-init-begin-local" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in local" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: volume + customInitContainers: | + - name: "custom-init-local" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in local" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: volume + # Add custom volumes + customVolumes: | + - name: custom-script-local + emptyDir: + sizeLimit: 100Mi + # Add custom volumesMounts + customVolumeMounts: | + - name: custom-script-local + mountPath: "/scriptslocal" + # Add custom sidecar containers + customSidecarContainers: | + - name: "sidecar-list-local" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in local' >> /scriptslocal/sidecarlocal.txt; cat /scriptslocal/sidecarlocal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scriptslocal" + name: custom-script-local + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +global: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + customInitContainersBegin: | + - name: "custom-init-begin-global" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in global" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: volume + customInitContainers: | + - name: "custom-init-global" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in global" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: volume + # Add custom volumes + customVolumes: | + - name: custom-script-global + emptyDir: + sizeLimit: 100Mi + # Add custom volumesMounts + customVolumeMounts: | + - name: custom-script-global + mountPath: "/scripts" + # Add custom sidecar containers + customSidecarContainers: | + - name: "sidecar-list-global" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in global' >> /scripts/sidecarglobal.txt; cat /scripts/sidecarglobal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scripts" + name: custom-script-global + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + +nginx: + customInitContainers: | + - name: "custom-init-begin-nginx" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in nginx" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: custom-script-local + customSidecarContainers: | + - name: "sidecar-list-nginx" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in local' >> /scriptslocal/sidecarlocal.txt; cat /scriptslocal/sidecarlocal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scriptslocal" + name: custom-script-local + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + # Add custom volumes + customVolumes: | + - name: custom-script-local + emptyDir: + sizeLimit: 100Mi + + artifactoryConf: | + {{- if .Values.nginx.https.enabled }} + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_certificate {{ .Values.nginx.persistence.mountPath }}/ssl/tls.crt; + ssl_certificate_key {{ .Values.nginx.persistence.mountPath }}/ssl/tls.key; + ssl_session_cache shared:SSL:1m; + ssl_prefer_server_ciphers on; + {{- end }} + ## server configuration + server { + listen 8088; + {{- if .Values.nginx.internalPortHttps }} + listen {{ .Values.nginx.internalPortHttps }} ssl; + {{- else -}} + {{- if .Values.nginx.https.enabled }} + listen {{ .Values.nginx.https.internalPort }} ssl; + {{- end }} + {{- end }} + {{- if .Values.nginx.internalPortHttp }} + listen {{ .Values.nginx.internalPortHttp }}; + {{- else -}} + {{- if .Values.nginx.http.enabled }} + listen {{ .Values.nginx.http.internalPort }}; + {{- end }} + {{- end }} + server_name ~(?.+)\.{{ include "artifactory-ha.fullname" . }} {{ include "artifactory-ha.fullname" . }} + {{- range .Values.ingress.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} + {{- end -}}; + if ($http_x_forwarded_proto = '') { + set $http_x_forwarded_proto $scheme; + } + ## Application specific logs + ## access_log /var/log/nginx/artifactory-access.log timing; + ## error_log /var/log/nginx/artifactory-error.log; + rewrite ^/artifactory/?$ / redirect; + if ( $repo != "" ) { + rewrite ^/(v1|v2)/(.*) /artifactory/api/docker/$repo/$1/$2 break; + } + chunked_transfer_encoding on; + client_max_body_size 0; + + location / { + proxy_read_timeout 900; + proxy_pass_header Server; + proxy_cookie_path ~*^/.* /; + proxy_pass {{ include "artifactory-ha.scheme" . }}://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalPort }}/; + {{- if .Values.nginx.service.ssloffload}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$server_port; + proxy_set_header X-Forwarded-Port $server_port; + {{- end }} + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location /artifactory/ { + if ( $request_uri ~ ^/artifactory/(.*)$ ) { + proxy_pass {{ include "artifactory-ha.scheme" . }}://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/$1; + } + proxy_pass {{ include "artifactory-ha.scheme" . }}://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/; + } + } + } + + ## A list of custom ports to expose on the NGINX pod. Follows the conventional Kubernetes yaml syntax for container ports. + customPorts: + - containerPort: 8088 + name: http2 + service: + ## A list of custom ports to expose through the Ingress controller service. Follows the conventional Kubernetes yaml syntax for service ports. + customPorts: + - port: 8088 + targetPort: 8088 + protocol: TCP + name: http2 diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/large-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/large-values.yaml new file mode 100644 index 0000000000..153307aa26 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/large-values.yaml @@ -0,0 +1,85 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + database: + maxOpenConnections: 150 + tomcat: + connector: + maxThreads: 300 + primary: + replicaCount: 4 + resources: + requests: + memory: "6Gi" + cpu: "2" + limits: + memory: "10Gi" + cpu: "8" + javaOpts: + xms: "8g" + xmx: "10g" +access: + database: + maxOpenConnections: 150 + tomcat: + connector: + maxThreads: 100 +router: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 150 + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/loggers-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/loggers-values.yaml new file mode 100644 index 0000000000..03c94be953 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/loggers-values.yaml @@ -0,0 +1,43 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + + loggers: + - access-audit.log + - access-request.log + - access-security-audit.log + - access-service.log + - artifactory-access.log + - artifactory-event.log + - artifactory-import-export.log + - artifactory-request.log + - artifactory-service.log + - frontend-request.log + - frontend-service.log + - metadata-request.log + - metadata-service.log + - router-request.log + - router-service.log + - router-traefik.log + + catalinaLoggers: + - tomcat-catalina.log + - tomcat-localhost.log diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/medium-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/medium-values.yaml new file mode 100644 index 0000000000..115e7d4602 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/medium-values.yaml @@ -0,0 +1,85 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + database: + maxOpenConnections: 100 + tomcat: + connector: + maxThreads: 200 + primary: + replicaCount: 3 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "8Gi" + cpu: "6" + javaOpts: + xms: "6g" + xmx: "8g" +access: + database: + maxOpenConnections: 100 + tomcat: + connector: + maxThreads: 50 +router: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 100 + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/migration-disabled-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/migration-disabled-values.yaml new file mode 100644 index 0000000000..44895a3731 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/migration-disabled-values.yaml @@ -0,0 +1,31 @@ +databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + migration: + enabled: false + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/nginx-autoreload-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/nginx-autoreload-values.yaml new file mode 100644 index 0000000000..a6f4e8001f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/nginx-autoreload-values.yaml @@ -0,0 +1,53 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true +## This is an exception here because HA needs masterKey to connect with other node members and it is commented in values to support 6.x to 7.x Migration +## Please refer https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/README.md#special-upgrade-notes-1 +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false + +nginx: + customVolumes: | + - name: scripts + configMap: + name: {{ template "artifactory-ha.fullname" . }}-nginx-scripts + defaultMode: 0550 + customVolumeMounts: | + - name: scripts + mountPath: /var/opt/jfrog/nginx/scripts/ + customCommand: + - /bin/sh + - -c + - | + # watch for configmap changes + /sbin/inotifyd /var/opt/jfrog/nginx/scripts/configreloader.sh {{ .Values.nginx.persistence.mountPath -}}/conf.d:n & + {{ if .Values.nginx.https.enabled -}} + # watch for tls secret changes + /sbin/inotifyd /var/opt/jfrog/nginx/scripts/configreloader.sh {{ .Values.nginx.persistence.mountPath -}}/ssl:n & + {{ end -}} + nginx -g 'daemon off;' diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-access-tls-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-access-tls-values.yaml new file mode 100644 index 0000000000..6f3b13cb14 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-access-tls-values.yaml @@ -0,0 +1,106 @@ +databaseUpgradeReady: true +artifactory: + replicaCount: 3 + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + +access: + accessConfig: + security: + tls: true + resetAccessCAKeys: true + +postgresql: + postgresqlPassword: password + postgresqlExtendedConf: + maxConnections: 102 + persistence: + enabled: false + +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true + +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false +jfconnect: + enabled: true + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +mc: + enabled: true +splitServicesToContainers: true + +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-values.yaml new file mode 100644 index 0000000000..87832a5051 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/rtsplit-values.yaml @@ -0,0 +1,155 @@ +databaseUpgradeReady: true +artifactory: + replicaCount: 3 + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + + # Add lifecycle hooks for artifactory container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the artifactory postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the artifactory postStart handler >> /tmp/message"] + +postgresql: + postgresqlPassword: password + postgresqlExtendedConf: + maxConnections: 102 + persistence: + enabled: false + +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true + +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false +jfconnect: + enabled: true + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for jfconect container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the jfconnect postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the jfconnect postStart handler >> /tmp/message"] + +mc: + enabled: true +splitServicesToContainers: true + +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for router container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the router postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the router postStart handler >> /tmp/message"] +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for frontend container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the frontend postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the frontend postStart handler >> /tmp/message"] +metadata: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the metadata postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the metadata postStart handler >> /tmp/message"] +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the event postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the event postStart handler >> /tmp/message"] +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the observability postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the observability postStart handler >> /tmp/message"] diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/small-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/small-values.yaml new file mode 100644 index 0000000000..b4557289ef --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/small-values.yaml @@ -0,0 +1,87 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 200 + primary: + replicaCount: 1 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "6g" + node: + replicaCount: 2 +access: + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 50 +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 80 + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-ha/107.90.9/ci/test-values.yaml b/charts/jfrog/artifactory-ha/107.90.9/ci/test-values.yaml new file mode 100644 index 0000000000..8bbbb5b3e5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/ci/test-values.yaml @@ -0,0 +1,85 @@ +databaseUpgradeReady: true +artifactory: + metrics: + enabled: true + podSecurityContext: + fsGroupChangePolicy: "OnRootMismatch" + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + unifiedSecretInstallation: false + persistence: + enabled: false + primary: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + node: + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + statefulset: + annotations: + artifactory: test + +postgresql: + postgresqlPassword: "password" + postgresqlExtendedConf: + maxConnections: "102" + persistence: + enabled: false +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false + +jfconnect: + enabled: false + +## filebeat sidecar +filebeat: + enabled: true + filebeatYml: | + logging.level: info + path.data: {{ .Values.artifactory.persistence.mountPath }}/log/filebeat + name: artifactory-filebeat + queue.spool: + file: + permissions: 0760 + filebeat.inputs: + - type: log + enabled: true + close_eof: ${CLOSE:false} + paths: + - {{ .Values.artifactory.persistence.mountPath }}/log/*.log + fields: + service: "jfrt" + log_type: "artifactory" + output.file: + path: "/tmp/filebeat" + filename: filebeat + readinessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + curl --fail 127.0.0.1:5066 diff --git a/charts/jfrog/artifactory-ha/107.90.9/files/binarystore.xml b/charts/jfrog/artifactory-ha/107.90.9/files/binarystore.xml new file mode 100644 index 0000000000..0e7bc5af0f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/files/binarystore.xml @@ -0,0 +1,439 @@ +{{- if and (eq .Values.artifactory.persistence.type "nfs") (.Values.artifactory.haDataDir.enabled) }} + + + + + + + +{{- end }} +{{- if and (eq .Values.artifactory.persistence.type "nfs") (not .Values.artifactory.haDataDir.enabled) }} + + {{- if (.Values.artifactory.persistence.maxCacheSize) }} + + + + + + {{- else }} + + + + {{- end }} + + {{- if .Values.artifactory.persistence.maxCacheSize }} + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + + + {{ .Values.artifactory.persistence.nfs.dataDir }}/filestore + + + +{{- end }} + +{{- if eq .Values.artifactory.persistence.type "file-system" }} + +{{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + + + + + + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) -}} + + {{- end }} + + + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + // Specify the read and write strategy and redundancy for the sharding binary provider + + roundRobin + percentageFreeSpace + 2 + + + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) -}} + //For each sub-provider (mount), specify the filestore location + + filestore{{ $sharedClaimNumber }} + + {{- end }} + +{{- else }} + + + + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + 2 + 2 + + + + + + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + + + shard-fs-1 + local + + + + + 30 + tester-remote1 + 10000 + remote + + + +{{- end }} +{{- end }} +{{- if or (eq .Values.artifactory.persistence.type "google-storage") (eq .Values.artifactory.persistence.type "google-storage-v2") (eq .Values.artifactory.persistence.type "google-storage-v2-direct") }} + + + {{- if or (eq .Values.artifactory.persistence.type "google-storage") (eq .Values.artifactory.persistence.type "google-storage-v2") }} + + + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + 2 + + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "google-storage-v2-direct" }} + + + + + + {{- end }} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{- if or (eq .Values.artifactory.persistence.type "google-storage") (eq .Values.artifactory.persistence.type "google-storage-v2") }} + + local + + + + + 30 + 10000 + remote + + {{- end }} + + + + {{- if .Values.artifactory.persistence.googleStorage.useInstanceCredentials }} + true + {{- else }} + false + {{- end }} + {{ .Values.artifactory.persistence.googleStorage.enableSignedUrlRedirect }} + google-cloud-storage + {{ .Values.artifactory.persistence.googleStorage.endpoint }} + {{ .Values.artifactory.persistence.googleStorage.httpsOnly }} + {{ .Values.artifactory.persistence.googleStorage.bucketName }} + {{ .Values.artifactory.persistence.googleStorage.path }} + {{ .Values.artifactory.persistence.googleStorage.bucketExists }} + + +{{- end }} +{{- if or (eq .Values.artifactory.persistence.type "aws-s3-v3") (eq .Values.artifactory.persistence.type "s3-storage-v3-direct") (eq .Values.artifactory.persistence.type "s3-storage-v3-archive") }} + + + {{- if eq .Values.artifactory.persistence.type "aws-s3-v3" }} + + + + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "s3-storage-v3-direct" }} + + + + + + {{- else if eq .Values.artifactory.persistence.type "s3-storage-v3-archive" }} + + + + + + + {{- end }} + + {{- if eq .Values.artifactory.persistence.type "aws-s3-v3" }} + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + + + + + remote + + + + local + + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + + {{- if eq .Values.artifactory.persistence.type "s3-storage-v3-direct" }} + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + + {{- with .Values.artifactory.persistence.awsS3V3 }} + + {{ .testConnection }} + {{- if .identity }} + {{ .identity }} + {{- end }} + {{- if .credential }} + {{ .credential }} + {{- end }} + {{ .region }} + {{ .bucketName }} + {{ .path }} + {{ .endpoint }} + {{- with .port }} + {{ . }} + {{- end }} + {{- with .useHttp }} + {{ . }} + {{- end }} + {{- with .maxConnections }} + {{ . }} + {{- end }} + {{- with .connectionTimeout }} + {{ . }} + {{- end }} + {{- with .socketTimeout }} + {{ . }} + {{- end }} + {{- with .kmsServerSideEncryptionKeyId }} + {{ . }} + {{- end }} + {{- with .kmsKeyRegion }} + {{ . }} + {{- end }} + {{- with .kmsCryptoMode }} + {{ . }} + {{- end }} + {{- if .useInstanceCredentials }} + true + {{- else }} + false + {{- end }} + {{ .usePresigning }} + {{ .signatureExpirySeconds }} + {{ .signedUrlExpirySeconds }} + {{- with .cloudFrontDomainName }} + {{ . }} + {{- end }} + {{- with .cloudFrontKeyPairId }} + {{ . }} + {{- end }} + {{- with .cloudFrontPrivateKey }} + {{ . }} + {{- end }} + {{- with .enableSignedUrlRedirect }} + {{ . }} + {{- end }} + {{- with .enablePathStyleAccess }} + {{ . }} + {{- end }} + {{- with .multiPartLimit }} + {{ . | int64 }} + {{- end }} + {{- with .multipartElementSize }} + {{ . | int64 }} + {{- end }} + + {{- end }} + +{{- end }} + +{{- if or (eq .Values.artifactory.persistence.type "azure-blob") (eq .Values.artifactory.persistence.type "azure-blob-storage-direct") }} + + + {{- if eq .Values.artifactory.persistence.type "azure-blob" }} + + + + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "azure-blob-storage-direct" }} + + + + + + {{- end }} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{- if eq .Values.artifactory.persistence.type "azure-blob" }} + + + crossNetworkStrategy + crossNetworkStrategy + 2 + 1 + + + + + remote + + + + local + + {{- end }} + + + + {{ .Values.artifactory.persistence.azureBlob.accountName }} + {{ .Values.artifactory.persistence.azureBlob.accountKey }} + {{ .Values.artifactory.persistence.azureBlob.endpoint }} + {{ .Values.artifactory.persistence.azureBlob.containerName }} + {{ .Values.artifactory.persistence.azureBlob.multiPartLimit | int64 }} + {{ .Values.artifactory.persistence.azureBlob.multipartElementSize | int64 }} + {{ .Values.artifactory.persistence.azureBlob.testConnection }} + + +{{- end }} +{{- if eq .Values.artifactory.persistence.type "azure-blob-storage-v2-direct" -}} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{ .Values.artifactory.persistence.azureBlob.accountName }} + {{ .Values.artifactory.persistence.azureBlob.accountKey }} + {{ .Values.artifactory.persistence.azureBlob.endpoint }} + {{ .Values.artifactory.persistence.azureBlob.containerName }} + {{ .Values.artifactory.persistence.azureBlob.multiPartLimit | int64 }} + {{ .Values.artifactory.persistence.azureBlob.multipartElementSize | int64 }} + {{ .Values.artifactory.persistence.azureBlob.testConnection }} + + +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/files/installer-info.json b/charts/jfrog/artifactory-ha/107.90.9/files/installer-info.json new file mode 100644 index 0000000000..cf6b020fb3 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/files/installer-info.json @@ -0,0 +1,32 @@ +{ + "productId": "Helm_artifactory-ha/{{ .Chart.Version }}", + "features": [ + { + "featureId": "Platform/{{ printf "%s-%s" "kubernetes" .Capabilities.KubeVersion.Version }}" + }, + { + "featureId": "Database/{{ .Values.database.type }}" + }, + { + "featureId": "PostgreSQL_Enabled/{{ .Values.postgresql.enabled }}" + }, + { + "featureId": "Nginx_Enabled/{{ .Values.nginx.enabled }}" + }, + { + "featureId": "ArtifactoryPersistence_Type/{{ .Values.artifactory.persistence.type }}" + }, + { + "featureId": "SplitServicesToContainers_Enabled/{{ .Values.splitServicesToContainers }}" + }, + { + "featureId": "UnifiedSecretInstallation_Enabled/{{ .Values.artifactory.unifiedSecretInstallation }}" + }, + { + "featureId": "Filebeat_Enabled/{{ .Values.filebeat.enabled }}" + }, + { + "featureId": "ReplicaCount/{{ add .Values.artifactory.primary.replicaCount .Values.artifactory.node.replicaCount }}" + } + ] +} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/files/migrate.sh b/charts/jfrog/artifactory-ha/107.90.9/files/migrate.sh new file mode 100644 index 0000000000..ba44160f47 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/files/migrate.sh @@ -0,0 +1,4311 @@ +#!/bin/bash + +# Flags +FLAG_Y="y" +FLAG_N="n" +FLAGS_Y_N="$FLAG_Y $FLAG_N" +FLAG_NOT_APPLICABLE="_NA_" + +CURRENT_VERSION=$1 + +WRAPPER_SCRIPT_TYPE_RPMDEB="RPMDEB" +WRAPPER_SCRIPT_TYPE_DOCKER_COMPOSE="DOCKERCOMPOSE" + +SENSITIVE_KEY_VALUE="__sensitive_key_hidden___" + +# Shared system keys +SYS_KEY_SHARED_JFROGURL="shared.jfrogUrl" +SYS_KEY_SHARED_SECURITY_JOINKEY="shared.security.joinKey" +SYS_KEY_SHARED_SECURITY_MASTERKEY="shared.security.masterKey" + +SYS_KEY_SHARED_NODE_ID="shared.node.id" +SYS_KEY_SHARED_JAVAHOME="shared.javaHome" + +SYS_KEY_SHARED_DATABASE_TYPE="shared.database.type" +SYS_KEY_SHARED_DATABASE_TYPE_VALUE_POSTGRES="postgresql" +SYS_KEY_SHARED_DATABASE_DRIVER="shared.database.driver" +SYS_KEY_SHARED_DATABASE_URL="shared.database.url" +SYS_KEY_SHARED_DATABASE_USERNAME="shared.database.username" +SYS_KEY_SHARED_DATABASE_PASSWORD="shared.database.password" + +SYS_KEY_SHARED_ELASTICSEARCH_URL="shared.elasticsearch.url" +SYS_KEY_SHARED_ELASTICSEARCH_USERNAME="shared.elasticsearch.username" +SYS_KEY_SHARED_ELASTICSEARCH_PASSWORD="shared.elasticsearch.password" +SYS_KEY_SHARED_ELASTICSEARCH_CLUSTERSETUP="shared.elasticsearch.clusterSetup" +SYS_KEY_SHARED_ELASTICSEARCH_UNICASTFILE="shared.elasticsearch.unicastFile" +SYS_KEY_SHARED_ELASTICSEARCH_CLUSTERSETUP_VALUE="YES" + +# Define this in product specific script. Should contain the path to unitcast file +# File used by insight server to write cluster active nodes info. This will be read by elasticsearch +#SYS_KEY_SHARED_ELASTICSEARCH_UNICASTFILE_VALUE="" + +SYS_KEY_RABBITMQ_ACTIVE_NODE_NAME="shared.rabbitMq.active.node.name" +SYS_KEY_RABBITMQ_ACTIVE_NODE_IP="shared.rabbitMq.active.node.ip" + +# Filenames +FILE_NAME_SYSTEM_YAML="system.yaml" +FILE_NAME_JOIN_KEY="join.key" +FILE_NAME_MASTER_KEY="master.key" +FILE_NAME_INSTALLER_YAML="installer.yaml" + +# Global constants used in business logic +NODE_TYPE_STANDALONE="standalone" +NODE_TYPE_CLUSTER_NODE="node" +NODE_TYPE_DATABASE="database" + +# External(isable) databases +DATABASE_POSTGRES="POSTGRES" +DATABASE_ELASTICSEARCH="ELASTICSEARCH" +DATABASE_RABBITMQ="RABBITMQ" + +POSTGRES_LABEL="PostgreSQL" +ELASTICSEARCH_LABEL="Elasticsearch" +RABBITMQ_LABEL="Rabbitmq" + +ARTIFACTORY_LABEL="Artifactory" +JFMC_LABEL="Mission Control" +DISTRIBUTION_LABEL="Distribution" +XRAY_LABEL="Xray" + +POSTGRES_CONTAINER="postgres" +ELASTICSEARCH_CONTAINER="elasticsearch" +RABBITMQ_CONTAINER="rabbitmq" +REDIS_CONTAINER="redis" + +#Adding a small timeout before a read ensures it is positioned correctly in the screen +read_timeout=0.5 + +# Options related to data directory location +PROMPT_DATA_DIR_LOCATION="Installation Directory" +KEY_DATA_DIR_LOCATION="installer.data_dir" + +SYS_KEY_SHARED_NODE_HAENABLED="shared.node.haEnabled" +PROMPT_ADD_TO_CLUSTER="Are you adding an additional node to an existing product cluster?" +KEY_ADD_TO_CLUSTER="installer.ha" +VALID_VALUES_ADD_TO_CLUSTER="$FLAGS_Y_N" + +MESSAGE_POSTGRES_INSTALL="The installer can install a $POSTGRES_LABEL database, or you can connect to an existing compatible $POSTGRES_LABEL database\n(compatible databases: https://www.jfrog.com/confluence/display/JFROG/System+Requirements#SystemRequirements-RequirementsMatrix)" +PROMPT_POSTGRES_INSTALL="Do you want to install $POSTGRES_LABEL?" +KEY_POSTGRES_INSTALL="installer.install_postgresql" +VALID_VALUES_POSTGRES_INSTALL="$FLAGS_Y_N" + +# Postgres connection details +RPM_DEB_POSTGRES_HOME_DEFAULT="/var/opt/jfrog/postgres" +RPM_DEB_MESSAGE_STANDALONE_POSTGRES_DATA="$POSTGRES_LABEL home will have data and its configuration" +RPM_DEB_PROMPT_STANDALONE_POSTGRES_DATA="Type desired $POSTGRES_LABEL home location" +RPM_DEB_KEY_STANDALONE_POSTGRES_DATA="installer.postgresql.home" + +MESSAGE_DATABASE_URL="Provide the database connection details" +PROMPT_DATABASE_URL(){ + local databaseURlExample= + case "$PRODUCT_NAME" in + $ARTIFACTORY_LABEL) + databaseURlExample="jdbc:postgresql://:/artifactory" + ;; + $JFMC_LABEL) + databaseURlExample="postgresql://:/mission_control?sslmode=disable" + ;; + $DISTRIBUTION_LABEL) + databaseURlExample="jdbc:postgresql://:/distribution?sslmode=disable" + ;; + $XRAY_LABEL) + databaseURlExample="postgres://:/xraydb?sslmode=disable" + ;; + esac + if [ -z "$databaseURlExample" ]; then + echo -n "$POSTGRES_LABEL URL" # For consistency with username and password + return + fi + echo -n "$POSTGRES_LABEL url. Example: [$databaseURlExample]" +} +REGEX_DATABASE_URL(){ + local databaseURlExample= + case "$PRODUCT_NAME" in + $ARTIFACTORY_LABEL) + databaseURlExample="jdbc:postgresql://.*/artifactory.*" + ;; + $JFMC_LABEL) + databaseURlExample="postgresql://.*/mission_control.*" + ;; + $DISTRIBUTION_LABEL) + databaseURlExample="jdbc:postgresql://.*/distribution.*" + ;; + $XRAY_LABEL) + databaseURlExample="postgres://.*/xraydb.*" + ;; + esac + echo -n "^$databaseURlExample\$" +} +ERROR_MESSAGE_DATABASE_URL="Invalid $POSTGRES_LABEL URL" +KEY_DATABASE_URL="$SYS_KEY_SHARED_DATABASE_URL" +#NOTE: It is important to display the label. Since the message may be hidden if URL is known +PROMPT_DATABASE_USERNAME="$POSTGRES_LABEL username" +KEY_DATABASE_USERNAME="$SYS_KEY_SHARED_DATABASE_USERNAME" +#NOTE: It is important to display the label. Since the message may be hidden if URL is known +PROMPT_DATABASE_PASSWORD="$POSTGRES_LABEL password" +KEY_DATABASE_PASSWORD="$SYS_KEY_SHARED_DATABASE_PASSWORD" +IS_SENSITIVE_DATABASE_PASSWORD="$FLAG_Y" + +MESSAGE_STANDALONE_ELASTICSEARCH_INSTALL="The installer can install a $ELASTICSEARCH_LABEL database or you can connect to an existing compatible $ELASTICSEARCH_LABEL database" +PROMPT_STANDALONE_ELASTICSEARCH_INSTALL="Do you want to install $ELASTICSEARCH_LABEL?" +KEY_STANDALONE_ELASTICSEARCH_INSTALL="installer.install_elasticsearch" +VALID_VALUES_STANDALONE_ELASTICSEARCH_INSTALL="$FLAGS_Y_N" + +# Elasticsearch connection details +MESSAGE_ELASTICSEARCH_DETAILS="Provide the $ELASTICSEARCH_LABEL connection details" +PROMPT_ELASTICSEARCH_URL="$ELASTICSEARCH_LABEL URL" +KEY_ELASTICSEARCH_URL="$SYS_KEY_SHARED_ELASTICSEARCH_URL" + +PROMPT_ELASTICSEARCH_USERNAME="$ELASTICSEARCH_LABEL username" +KEY_ELASTICSEARCH_USERNAME="$SYS_KEY_SHARED_ELASTICSEARCH_USERNAME" + +PROMPT_ELASTICSEARCH_PASSWORD="$ELASTICSEARCH_LABEL password" +KEY_ELASTICSEARCH_PASSWORD="$SYS_KEY_SHARED_ELASTICSEARCH_PASSWORD" +IS_SENSITIVE_ELASTICSEARCH_PASSWORD="$FLAG_Y" + +# Cluster related questions +MESSAGE_CLUSTER_MASTER_KEY="Provide the cluster's master key. It can be found in the data directory of the first node under /etc/security/master.key" +PROMPT_CLUSTER_MASTER_KEY="Master Key" +KEY_CLUSTER_MASTER_KEY="$SYS_KEY_SHARED_SECURITY_MASTERKEY" +IS_SENSITIVE_CLUSTER_MASTER_KEY="$FLAG_Y" + +MESSAGE_JOIN_KEY="The Join key is the secret key used to establish trust between services in the JFrog Platform.\n(You can copy the Join Key from Admin > User Management > Settings)" +PROMPT_JOIN_KEY="Join Key" +KEY_JOIN_KEY="$SYS_KEY_SHARED_SECURITY_JOINKEY" +IS_SENSITIVE_JOIN_KEY="$FLAG_Y" +REGEX_JOIN_KEY="^[a-zA-Z0-9]{16,}\$" +ERROR_MESSAGE_JOIN_KEY="Invalid Join Key" + +# Rabbitmq related cluster information +MESSAGE_RABBITMQ_ACTIVE_NODE_NAME="Provide an active ${RABBITMQ_LABEL} node name. Run the command [ hostname -s ] on any of the existing nodes in the product cluster to get this" +PROMPT_RABBITMQ_ACTIVE_NODE_NAME="${RABBITMQ_LABEL} active node name" +KEY_RABBITMQ_ACTIVE_NODE_NAME="$SYS_KEY_RABBITMQ_ACTIVE_NODE_NAME" + +# Rabbitmq related cluster information (necessary only for docker-compose) +PROMPT_RABBITMQ_ACTIVE_NODE_IP="${RABBITMQ_LABEL} active node ip" +KEY_RABBITMQ_ACTIVE_NODE_IP="$SYS_KEY_RABBITMQ_ACTIVE_NODE_IP" + +MESSAGE_JFROGURL(){ + echo -e "The JFrog URL allows ${PRODUCT_NAME} to connect to a JFrog Platform Instance.\n(You can copy the JFrog URL from Administration > User Management > Settings > Connection details)" +} +PROMPT_JFROGURL="JFrog URL" +KEY_JFROGURL="$SYS_KEY_SHARED_JFROGURL" +REGEX_JFROGURL="^https?://.*:{0,}[0-9]{0,4}\$" +ERROR_MESSAGE_JFROGURL="Invalid JFrog URL" + + +# Set this to FLAG_Y on upgrade +IS_UPGRADE="${FLAG_N}" + +# This belongs in JFMC but is the ONLY one that needs it so keeping it here for now. Can be made into a method and overridden if necessary +MESSAGE_MULTIPLE_PG_SCHEME="Please setup $POSTGRES_LABEL with schema as described in https://www.jfrog.com/confluence/display/JFROG/Installing+Mission+Control" + +_getMethodOutputOrVariableValue() { + unset EFFECTIVE_MESSAGE + local keyToSearch=$1 + local effectiveMessage= + local result="0" + # logSilly "Searching for method: [$keyToSearch]" + LC_ALL=C type "$keyToSearch" > /dev/null 2>&1 || result="$?" + if [[ "$result" == "0" ]]; then + # logSilly "Found method for [$keyToSearch]" + EFFECTIVE_MESSAGE="$($keyToSearch)" + return + fi + eval EFFECTIVE_MESSAGE=\${$keyToSearch} + if [ ! -z "$EFFECTIVE_MESSAGE" ]; then + return + fi + # logSilly "Didn't find method or variable for [$keyToSearch]" +} + + +# REF https://misc.flogisoft.com/bash/tip_colors_and_formatting +cClear="\e[0m" +cBlue="\e[38;5;69m" +cRedDull="\e[1;31m" +cYellow="\e[1;33m" +cRedBright="\e[38;5;197m" +cBold="\e[1m" + + +_loggerGetModeRaw() { + local MODE="$1" + case $MODE in + INFO) + printf "" + ;; + DEBUG) + printf "%s" "[${MODE}] " + ;; + WARN) + printf "${cRedDull}%s%s${cClear}" "[" "${MODE}" "] " + ;; + ERROR) + printf "${cRedBright}%s%s${cClear}" "[" "${MODE}" "] " + ;; + esac +} + + +_loggerGetMode() { + local MODE="$1" + case $MODE in + INFO) + printf "${cBlue}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + DEBUG) + printf "%-7s" "[${MODE}]" + ;; + WARN) + printf "${cRedDull}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + ERROR) + printf "${cRedBright}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + esac +} + +# Capitalises the first letter of the message +_loggerGetMessage() { + local originalMessage="$*" + local firstChar=$(echo "${originalMessage:0:1}" | awk '{ print toupper($0) }') + local resetOfMessage="${originalMessage:1}" + echo "$firstChar$resetOfMessage" +} + +# The spec also says content should be left-trimmed but this is not necessary in our case. We don't reach the limit. +_loggerGetStackTrace() { + printf "%s%-30s%s" "[" "$1:$2" "]" +} + +_loggerGetThread() { + printf "%s" "[main]" +} + +_loggerGetServiceType() { + printf "%s%-5s%s" "[" "shell" "]" +} + +#Trace ID is not applicable to scripts +_loggerGetTraceID() { + printf "%s" "[]" +} + +logRaw() { + echo "" + printf "$1" + echo "" +} + +logBold(){ + echo "" + printf "${cBold}$1${cClear}" + echo "" +} + +# The date binary works differently based on whether it is GNU/BSD +is_date_supported=0 +date --version > /dev/null 2>&1 || is_date_supported=1 +IS_GNU=$(echo $is_date_supported) + +_loggerGetTimestamp() { + if [ "${IS_GNU}" == "0" ]; then + echo -n $(date -u +%FT%T.%3NZ) + else + echo -n $(date -u +%FT%T.000Z) + fi +} + +# https://www.shellscript.sh/tips/spinner/ +_spin() +{ + spinner="/|\\-/|\\-" + while : + do + for i in `seq 0 7` + do + echo -n "${spinner:$i:1}" + echo -en "\010" + sleep 1 + done + done +} + +showSpinner() { + # Start the Spinner: + _spin & + # Make a note of its Process ID (PID): + SPIN_PID=$! + # Kill the spinner on any signal, including our own exit. + trap "kill -9 $SPIN_PID" `seq 0 15` &> /dev/null || return 0 +} + +stopSpinner() { + local occurrences=$(ps -ef | grep -wc "${SPIN_PID}") + let "occurrences+=0" + # validate that it is present (2 since this search itself will show up in the results) + if [ $occurrences -gt 1 ]; then + kill -9 $SPIN_PID &>/dev/null || return 0 + wait $SPIN_ID &>/dev/null + fi +} + +_getEffectiveMessage(){ + local MESSAGE="$1" + local MODE=${2-"INFO"} + + if [ -z "$CONTEXT" ]; then + CONTEXT=$(caller) + fi + + _EFFECTIVE_MESSAGE= + if [ -z "$LOG_BEHAVIOR_ADD_META" ]; then + _EFFECTIVE_MESSAGE="$(_loggerGetModeRaw $MODE)$(_loggerGetMessage $MESSAGE)" + else + local SERVICE_TYPE="script" + local TRACE_ID="" + local THREAD="main" + + local CONTEXT_LINE=$(echo "$CONTEXT" | awk '{print $1}') + local CONTEXT_FILE=$(echo "$CONTEXT" | awk -F"/" '{print $NF}') + + _EFFECTIVE_MESSAGE="$(_loggerGetTimestamp) $(_loggerGetServiceType) $(_loggerGetMode $MODE) $(_loggerGetTraceID) $(_loggerGetStackTrace $CONTEXT_FILE $CONTEXT_LINE) $(_loggerGetThread) - $(_loggerGetMessage $MESSAGE)" + fi + CONTEXT= +} + +# Important - don't call any log method from this method. Will become an infinite loop. Use echo to debug +_logToFile() { + local MODE=${1-"INFO"} + local targetFile="$LOG_BEHAVIOR_ADD_REDIRECTION" + # IF the file isn't passed, abort + if [ -z "$targetFile" ]; then + return + fi + # IF this is not being run in verbose mode and mode is debug or lower, abort + if [ "${VERBOSE_MODE}" != "$FLAG_Y" ] && [ "${VERBOSE_MODE}" != "true" ] && [ "${VERBOSE_MODE}" != "debug" ]; then + if [ "$MODE" == "DEBUG" ] || [ "$MODE" == "SILLY" ]; then + return + fi + fi + + # Create the file if it doesn't exist + if [ ! -f "${targetFile}" ]; then + return + # touch $targetFile > /dev/null 2>&1 || true + fi + # # Make it readable + # chmod 640 $targetFile > /dev/null 2>&1 || true + + # Log contents + printf "%s\n" "$_EFFECTIVE_MESSAGE" >> "$targetFile" || true +} + +logger() { + if [ "$LOG_BEHAVIOR_ADD_NEW_LINE" == "$FLAG_Y" ]; then + echo "" + fi + _getEffectiveMessage "$@" + local MODE=${2-"INFO"} + printf "%s\n" "$_EFFECTIVE_MESSAGE" + _logToFile "$MODE" +} + +logDebug(){ + VERBOSE_MODE=${VERBOSE_MODE-"false"} + CONTEXT=$(caller) + if [ "${VERBOSE_MODE}" == "$FLAG_Y" ] || [ "${VERBOSE_MODE}" == "true" ] || [ "${VERBOSE_MODE}" == "debug" ];then + logger "$1" "DEBUG" + else + logger "$1" "DEBUG" >&6 + fi + CONTEXT= +} + +logSilly(){ + VERBOSE_MODE=${VERBOSE_MODE-"false"} + CONTEXT=$(caller) + if [ "${VERBOSE_MODE}" == "silly" ];then + logger "$1" "DEBUG" + else + logger "$1" "DEBUG" >&6 + fi + CONTEXT= +} + +logError() { + CONTEXT=$(caller) + logger "$1" "ERROR" + CONTEXT= +} + +errorExit () { + CONTEXT=$(caller) + logger "$1" "ERROR" + CONTEXT= + exit 1 +} + +warn () { + CONTEXT=$(caller) + logger "$1" "WARN" + CONTEXT= +} + +note () { + CONTEXT=$(caller) + logger "$1" "NOTE" + CONTEXT= +} + +bannerStart() { + title=$1 + echo + echo -e "\033[1m${title}\033[0m" + echo +} + +bannerSection() { + title=$1 + echo + echo -e "******************************** ${title} ********************************" + echo +} + +bannerSubSection() { + title=$1 + echo + echo -e "************** ${title} *******************" + echo +} + +bannerMessge() { + title=$1 + echo + echo -e "********************************" + echo -e "${title}" + echo -e "********************************" + echo +} + +setRed () { + local input="$1" + echo -e \\033[31m${input}\\033[0m +} +setGreen () { + local input="$1" + echo -e \\033[32m${input}\\033[0m +} +setYellow () { + local input="$1" + echo -e \\033[33m${input}\\033[0m +} + +logger_addLinebreak () { + echo -e "---\n" +} + +bannerImportant() { + title=$1 + local bold="\033[1m" + local noColour="\033[0m" + echo + echo -e "${bold}######################################## IMPORTANT ########################################${noColour}" + echo -e "${bold}${title}${noColour}" + echo -e "${bold}###########################################################################################${noColour}" + echo +} + +bannerEnd() { + #TODO pass a title and calculate length dynamically so that start and end look alike + echo + echo "*****************************************************************************" + echo +} + +banner() { + title=$1 + content=$2 + bannerStart "${title}" + echo -e "$content" +} + +# The logic below helps us redirect content we'd normally hide to the log file. + # + # We have several commands which clutter the console with output and so use + # `cmd > /dev/null` - this redirects the command's output to null. + # + # However, the information we just hid maybe useful for support. Using the code pattern + # `cmd >&6` (instead of `cmd> >/dev/null` ), the command's output is hidden from the console + # but redirected to the installation log file + # + +#Default value of 6 is just null +exec 6>>/dev/null +redirectLogsToFile() { + echo "" + # local file=$1 + + # [ ! -z "${file}" ] || return 0 + + # local logDir=$(dirname "$file") + + # if [ ! -f "${file}" ]; then + # [ -d "${logDir}" ] || mkdir -p ${logDir} || \ + # ( echo "WARNING : Could not create parent directory (${logDir}) to redirect console log : ${file}" ; return 0 ) + # fi + + # #6 now points to the log file + # exec 6>>${file} + # #reference https://unix.stackexchange.com/questions/145651/using-exec-and-tee-to-redirect-logs-to-stdout-and-a-log-file-in-the-same-time + # exec 2>&1 > >(tee -a "${file}") +} + +# Check if a give key contains any sensitive string as part of it +# Based on the result, the caller can decide its value can be displayed or not +# Sample usage : isKeySensitive "${key}" && displayValue="******" || displayValue=${value} +isKeySensitive(){ + local key=$1 + local sensitiveKeys="password|secret|key|token" + + if [ -z "${key}" ]; then + return 1 + else + local lowercaseKey=$(echo "${key}" | tr '[:upper:]' '[:lower:]' 2>/dev/null) + [[ "${lowercaseKey}" =~ ${sensitiveKeys} ]] && return 0 || return 1 + fi +} + +getPrintableValueOfKey(){ + local displayValue= + local key="$1" + if [ -z "$key" ]; then + # This is actually an incorrect usage of this method but any logging will cause unexpected content in the caller + echo -n "" + return + fi + + local value="$2" + isKeySensitive "${key}" && displayValue="$SENSITIVE_KEY_VALUE" || displayValue="${value}" + echo -n $displayValue +} + +_createConsoleLog(){ + if [ -z "${JF_PRODUCT_HOME}" ]; then + return + fi + local targetFile="${JF_PRODUCT_HOME}/var/log/console.log" + mkdir -p "${JF_PRODUCT_HOME}/var/log" || true + if [ ! -f ${targetFile} ]; then + touch $targetFile > /dev/null 2>&1 || true + fi + chmod 640 $targetFile > /dev/null 2>&1 || true +} + +# Output from application's logs are piped to this method. It checks a configuration variable to determine if content should be logged to +# the common console.log file +redirectServiceLogsToFile() { + + local result="0" + # check if the function getSystemValue exists + LC_ALL=C type getSystemValue > /dev/null 2>&1 || result="$?" + if [[ "$result" != "0" ]]; then + warn "Couldn't find the systemYamlHelper. Skipping log redirection" + return 0 + fi + + getSystemValue "shared.consoleLog" "NOT_SET" + if [[ "${YAML_VALUE}" == "false" ]]; then + logger "Redirection is set to false. Skipping log redirection" + return 0; + fi + + if [ -z "${JF_PRODUCT_HOME}" ] || [ "${JF_PRODUCT_HOME}" == "" ]; then + warn "JF_PRODUCT_HOME is unavailable. Skipping log redirection" + return 0 + fi + + local targetFile="${JF_PRODUCT_HOME}/var/log/console.log" + + _createConsoleLog + + while read -r line; do + printf '%s\n' "${line}" >> $targetFile || return 0 # Don't want to log anything - might clutter the screen + done +} + +## Display environment variables starting with JF_ along with its value +## Value of sensitive keys will be displayed as "******" +## +## Sample Display : +## +## ======================== +## JF Environment variables +## ======================== +## +## JF_SHARED_NODE_ID : locahost +## JF_SHARED_JOINKEY : ****** +## +## +displayEnv() { + local JFEnv=$(printenv | grep ^JF_ 2>/dev/null) + local key= + local value= + + if [ -z "${JFEnv}" ]; then + return + fi + + cat << ENV_START_MESSAGE + +======================== +JF Environment variables +======================== +ENV_START_MESSAGE + + for entry in ${JFEnv}; do + key=$(echo "${entry}" | awk -F'=' '{print $1}') + value=$(echo "${entry}" | awk -F'=' '{print $2}') + + isKeySensitive "${key}" && value="******" || value=${value} + + printf "\n%-35s%s" "${key}" " : ${value}" + done + echo; +} + +_addLogRotateConfiguration() { + logDebug "Method ${FUNCNAME[0]}" + # mandatory inputs + local confFile="$1" + local logFile="$2" + + # Method available in _ioOperations.sh + LC_ALL=C type io_setYQPath > /dev/null 2>&1 || return 1 + + io_setYQPath + + # Method available in _systemYamlHelper.sh + LC_ALL=C type getSystemValue > /dev/null 2>&1 || return 1 + + local frequency="daily" + local archiveFolder="archived" + + local compressLogFiles= + getSystemValue "shared.logging.rotation.compress" "true" + if [[ "${YAML_VALUE}" == "true" ]]; then + compressLogFiles="compress" + fi + + getSystemValue "shared.logging.rotation.maxFiles" "10" + local noOfBackupFiles="${YAML_VALUE}" + + getSystemValue "shared.logging.rotation.maxSizeMb" "25" + local sizeOfFile="${YAML_VALUE}M" + + logDebug "Adding logrotate configuration for [$logFile] to [$confFile]" + + # Add configuration to file + local confContent=$(cat << LOGROTATECONF +$logFile { + $frequency + missingok + rotate $noOfBackupFiles + $compressLogFiles + notifempty + olddir $archiveFolder + dateext + extension .log + dateformat -%Y-%m-%d + size ${sizeOfFile} +} +LOGROTATECONF +) + echo "${confContent}" > ${confFile} || return 1 +} + +_operationIsBySameUser() { + local targetUser="$1" + local currentUserID=$(id -u) + local currentUserName=$(id -un) + + if [ $currentUserID == $targetUser ] || [ $currentUserName == $targetUser ]; then + echo -n "yes" + else + echo -n "no" + fi +} + +_addCronJobForLogrotate() { + logDebug "Method ${FUNCNAME[0]}" + + # Abort if logrotate is not available + [ "$(io_commandExists 'crontab')" != "yes" ] && warn "cron is not available" && return 1 + + # mandatory inputs + local productHome="$1" + local confFile="$2" + local cronJobOwner="$3" + + # We want to use our binary if possible. It may be more recent than the one in the OS + local logrotateBinary="$productHome/app/third-party/logrotate/logrotate" + + if [ ! -f "$logrotateBinary" ]; then + logrotateBinary="logrotate" + [ "$(io_commandExists 'logrotate')" != "yes" ] && warn "logrotate is not available" && return 1 + fi + local cmd="$logrotateBinary ${confFile} --state $productHome/var/etc/logrotate/logrotate-state" #--verbose + + id -u $cronJobOwner > /dev/null 2>&1 || { warn "User $cronJobOwner does not exist. Aborting logrotate configuration" && return 1; } + + # Remove the existing line + removeLogRotation "$productHome" "$cronJobOwner" || true + + # Run logrotate daily at 23:55 hours + local cronInterval="55 23 * * * $cmd" + + local standaloneMode=$(_operationIsBySameUser "$cronJobOwner") + + # If this is standalone mode, we cannot use -u - the user running this process may not have the necessary privileges + if [ "$standaloneMode" == "no" ]; then + (crontab -l -u $cronJobOwner 2>/dev/null; echo "$cronInterval") | crontab -u $cronJobOwner - + else + (crontab -l 2>/dev/null; echo "$cronInterval") | crontab - + fi +} + +## Configure logrotate for a product +## Failure conditions: +## If logrotation could not be setup for some reason +## Parameters: +## $1: The product name +## $2: The product home +## Depends on global: none +## Updates global: none +## Returns: NA + +configureLogRotation() { + logDebug "Method ${FUNCNAME[0]}" + + # mandatory inputs + local productName="$1" + if [ -z $productName ]; then + warn "Incorrect usage. A product name is necessary for configuring log rotation" && return 1 + fi + + local productHome="$2" + if [ -z $productHome ]; then + warn "Incorrect usage. A product home folder is necessary for configuring log rotation" && return 1 + fi + + local logFile="${productHome}/var/log/console.log" + if [[ $(uname) == "Darwin" ]]; then + logger "Log rotation for [$logFile] has not been configured. Please setup manually" + return 0 + fi + + local userID="$3" + if [ -z $userID ]; then + warn "Incorrect usage. A userID is necessary for configuring log rotation" && return 1 + fi + + local groupID=${4:-$userID} + local logConfigOwner=${5:-$userID} + + logDebug "Configuring log rotation as user [$userID], group [$groupID], effective cron User [$logConfigOwner]" + + local errorMessage="Could not configure logrotate. Please configure log rotation of the file: [$logFile] manually" + + local confFile="${productHome}/var/etc/logrotate/logrotate.conf" + + # TODO move to recursive method + createDir "${productHome}" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/log" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/log/archived" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + + # TODO move to recursive method + createDir "${productHome}/var/etc" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/etc/logrotate" "$logConfigOwner" || { warn "${errorMessage}" && return 1; } + + # conf file should be owned by the user running the script + createFile "${confFile}" "${logConfigOwner}" || { warn "Could not create configuration file [$confFile]" return 1; } + + _addLogRotateConfiguration "${confFile}" "${logFile}" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + _addCronJobForLogrotate "${productHome}" "${confFile}" "${logConfigOwner}" || { warn "${errorMessage}" && return 1; } +} + +_pauseExecution() { + if [ "${VERBOSE_MODE}" == "debug" ]; then + + local breakPoint="$1" + if [ ! -z "$breakPoint" ]; then + printf "${cBlue}Breakpoint${cClear} [$breakPoint] " + echo "" + fi + printf "${cBlue}Press enter once you are ready to continue${cClear}" + read -s choice + echo "" + fi +} + +# removeLogRotation "$productHome" "$cronJobOwner" || true +removeLogRotation() { + logDebug "Method ${FUNCNAME[0]}" + if [[ $(uname) == "Darwin" ]]; then + logDebug "Not implemented for Darwin." + return 0 + fi + local productHome="$1" + local cronJobOwner="$2" + local standaloneMode=$(_operationIsBySameUser "$cronJobOwner") + + local confFile="${productHome}/var/etc/logrotate/logrotate.conf" + + if [ "$standaloneMode" == "no" ]; then + crontab -l -u $cronJobOwner 2>/dev/null | grep -v "$confFile" | crontab -u $cronJobOwner - + else + crontab -l 2>/dev/null | grep -v "$confFile" | crontab - + fi +} + +# NOTE: This method does not check the configuration to see if redirection is necessary. +# This is intentional. If we don't redirect, tomcat logs might get redirected to a folder/file +# that does not exist, causing the service itself to not start +setupTomcatRedirection() { + logDebug "Method ${FUNCNAME[0]}" + local consoleLog="${JF_PRODUCT_HOME}/var/log/console.log" + _createConsoleLog + export CATALINA_OUT="${consoleLog}" +} + +setupScriptLogsRedirection() { + logDebug "Method ${FUNCNAME[0]}" + if [ -z "${JF_PRODUCT_HOME}" ]; then + logDebug "No JF_PRODUCT_HOME. Returning" + return + fi + # Create the console.log file if it is not already present + # _createConsoleLog || true + # # Ensure any logs (logger/logError/warn) also get redirected to the console.log + # # Using installer.log as a temparory fix. Please change this to console.log once INST-291 is fixed + export LOG_BEHAVIOR_ADD_REDIRECTION="${JF_PRODUCT_HOME}/var/log/console.log" + export LOG_BEHAVIOR_ADD_META="$FLAG_Y" +} + +# Returns Y if this method is run inside a container +isRunningInsideAContainer() { + local check1=$(grep -sq 'docker\|kubepods' /proc/1/cgroup; echo $?) + local check2=$(grep -sq 'containers' /proc/self/mountinfo; echo $?) + if [[ $check1 == 0 || $check2 == 0 || -f "/.dockerenv" ]]; then + echo -n "$FLAG_Y" + else + echo -n "$FLAG_N" + fi +} + +POSTGRES_USER=999 +NGINX_USER=104 +NGINX_GROUP=107 +ES_USER=1000 +REDIS_USER=999 +MONGO_USER=999 +RABBITMQ_USER=999 +LOG_FILE_PERMISSION=640 +PID_FILE_PERMISSION=644 + +# Copy file +copyFile(){ + local source=$1 + local target=$2 + local mode=${3:-overwrite} + local enableVerbose=${4:-"${FLAG_N}"} + local verboseFlag="" + + if [ ! -z "${enableVerbose}" ] && [ "${enableVerbose}" == "${FLAG_Y}" ]; then + verboseFlag="-v" + fi + + if [[ ! ( $source && $target ) ]]; then + warn "Source and target is mandatory to copy file" + return 1 + fi + + if [[ -f "${target}" ]]; then + [[ "$mode" = "overwrite" ]] && ( cp ${verboseFlag} -f "$source" "$target" || errorExit "Unable to copy file, command : cp -f ${source} ${target}") || true + else + cp ${verboseFlag} -f "$source" "$target" || errorExit "Unable to copy file, command : cp -f ${source} ${target}" + fi +} + +# Copy files recursively from given source directory to destination directory +# This method wil copy but will NOT overwrite +# Destination will be created if its not available +copyFilesNoOverwrite(){ + local src=$1 + local dest=$2 + local enableVerboseCopy="${3:-${FLAG_Y}}" + + if [[ -z "${src}" || -z "${dest}" ]]; then + return + fi + + if [ -d "${src}" ] && [ "$(ls -A ${src})" ]; then + local relativeFilePath="" + local targetFilePath="" + + for file in $(find ${src} -type f 2>/dev/null) ; do + # Derive relative path and attach it to destination + # Example : + # src=/extra_config + # dest=/var/opt/jfrog/artifactory/etc + # file=/extra_config/config.xml + # relativeFilePath=config.xml + # targetFilePath=/var/opt/jfrog/artifactory/etc/config.xml + relativeFilePath=${file/${src}/} + targetFilePath=${dest}${relativeFilePath} + + createDir "$(dirname "$targetFilePath")" + copyFile "${file}" "${targetFilePath}" "no_overwrite" "${enableVerboseCopy}" + done + fi +} + +# TODO : WINDOWS ? +# Check the max open files and open processes set on the system +checkULimits () { + local minMaxOpenFiles=${1:-32000} + local minMaxOpenProcesses=${2:-1024} + local setValue=${3:-true} + local warningMsgForFiles=${4} + local warningMsgForProcesses=${5} + + logger "Checking open files and processes limits" + + local currentMaxOpenFiles=$(ulimit -n) + logger "Current max open files is $currentMaxOpenFiles" + if [ ${currentMaxOpenFiles} != "unlimited" ] && [ "$currentMaxOpenFiles" -lt "$minMaxOpenFiles" ]; then + if [ "${setValue}" ]; then + ulimit -n "${minMaxOpenFiles}" >/dev/null 2>&1 || warn "Max number of open files $currentMaxOpenFiles is low!" + [ -z "${warningMsgForFiles}" ] || warn "${warningMsgForFiles}" + else + errorExit "Max number of open files $currentMaxOpenFiles, is too low. Cannot run the application!" + fi + fi + + local currentMaxOpenProcesses=$(ulimit -u) + logger "Current max open processes is $currentMaxOpenProcesses" + if [ "$currentMaxOpenProcesses" != "unlimited" ] && [ "$currentMaxOpenProcesses" -lt "$minMaxOpenProcesses" ]; then + if [ "${setValue}" ]; then + ulimit -u "${minMaxOpenProcesses}" >/dev/null 2>&1 || warn "Max number of open files $currentMaxOpenFiles is low!" + [ -z "${warningMsgForProcesses}" ] || warn "${warningMsgForProcesses}" + else + errorExit "Max number of open files $currentMaxOpenProcesses, is too low. Cannot run the application!" + fi + fi +} + +createDirs() { + local appDataDir=$1 + local serviceName=$2 + local folders="backup bootstrap data etc logs work" + + [ -z "${appDataDir}" ] && errorExit "An application directory is mandatory to create its data structure" || true + [ -z "${serviceName}" ] && errorExit "A service name is mandatory to create service data structure" || true + + for folder in ${folders} + do + folder=${appDataDir}/${folder}/${serviceName} + if [ ! -d "${folder}" ]; then + logger "Creating folder : ${folder}" + mkdir -p "${folder}" || errorExit "Failed to create ${folder}" + fi + done +} + + +testReadWritePermissions () { + local dir_to_check=$1 + local error=false + + [ -d ${dir_to_check} ] || errorExit "'${dir_to_check}' is not a directory" + + local test_file=${dir_to_check}/test-permissions + + # Write file + if echo test > ${test_file} 1> /dev/null 2>&1; then + # Write succeeded. Testing read... + if cat ${test_file} > /dev/null; then + rm -f ${test_file} + else + error=true + fi + else + error=true + fi + + if [ ${error} == true ]; then + return 1 + else + return 0 + fi +} + +# Test directory has read/write permissions for current user +testDirectoryPermissions () { + local dir_to_check=$1 + local error=false + + [ -d ${dir_to_check} ] || errorExit "'${dir_to_check}' is not a directory" + + local u_id=$(id -u) + local id_str="id ${u_id}" + + logger "Testing directory ${dir_to_check} has read/write permissions for user ${id_str}" + + if ! testReadWritePermissions ${dir_to_check}; then + error=true + fi + + if [ "${error}" == true ]; then + local stat_data=$(stat -Lc "Directory: %n, permissions: %a, owner: %U, group: %G" ${dir_to_check}) + logger "###########################################################" + logger "${dir_to_check} DOES NOT have proper permissions for user ${id_str}" + logger "${stat_data}" + logger "Mounted directory must have read/write permissions for user ${id_str}" + logger "###########################################################" + errorExit "Directory ${dir_to_check} has bad permissions for user ${id_str}" + fi + logger "Permissions for ${dir_to_check} are good" +} + +# Utility method to create a directory path recursively with chown feature as +# Failure conditions: +## Exits if unable to create a directory +# Parameters: +## $1: Root directory from where the path can be created +## $2: List of recursive child directories separated by space +## $3: user who should own the directory. Optional +## $4: group who should own the directory. Optional +# Depends on global: none +# Updates global: none +# Returns: NA +# +# Usage: +# createRecursiveDir "/opt/jfrog/product/var" "bootstrap tomcat lib" "user_name" "group_name" +createRecursiveDir(){ + local rootDir=$1 + local pathDirs=$2 + local user=$3 + local group=${4:-${user}} + local fullPath= + + [ ! -z "${rootDir}" ] || return 0 + + createDir "${rootDir}" "${user}" "${group}" + + [ ! -z "${pathDirs}" ] || return 0 + + fullPath=${rootDir} + + for dir in ${pathDirs}; do + fullPath=${fullPath}/${dir} + createDir "${fullPath}" "${user}" "${group}" + done +} + +# Utility method to create a directory +# Failure conditions: +## Exits if unable to create a directory +# Parameters: +## $1: directory to create +## $2: user who should own the directory. Optional +## $3: group who should own the directory. Optional +# Depends on global: none +# Updates global: none +# Returns: NA + +createDir(){ + local dirName="$1" + local printMessage=no + logSilly "Method ${FUNCNAME[0]} invoked with [$dirName]" + [ -z "${dirName}" ] && return + + logDebug "Attempting to create ${dirName}" + mkdir -p "${dirName}" || errorExit "Unable to create directory: [${dirName}]" + local userID="$2" + local groupID=${3:-$userID} + + # If UID/GID is passed, chown the folder + if [ ! -z "$userID" ] && [ ! -z "$groupID" ]; then + # Earlier, this line would have returned 1 if it failed. Now it just warns. + # This is intentional. Earlier, this line would NOT be reached if the folder already existed. + # Since it will always come to this line and the script may be running as a non-root user, this method will just warn if + # setting permissions fails (so as to not affect any existing flows) + io_setOwnershipNonRecursive "$dirName" "$userID" "$groupID" || warn "Could not set owner of [$dirName] to [$userID:$groupID]" + fi + # logging message to print created dir with user and group + local logMessage=${4:-$printMessage} + if [[ "${logMessage}" == "yes" ]]; then + logger "Successfully created directory [${dirName}]. Owner: [${userID}:${groupID}]" + fi +} + +removeSoftLinkAndCreateDir () { + local dirName="$1" + local userID="$2" + local groupID="$3" + local logMessage="$4" + removeSoftLink "${dirName}" + createDir "${dirName}" "${userID}" "${groupID}" "${logMessage}" +} + +# Utility method to remove a soft link +removeSoftLink () { + local dirName="$1" + if [[ -L "${dirName}" ]]; then + targetLink=$(readlink -f "${dirName}") + logger "Removing the symlink [${dirName}] pointing to [${targetLink}]" + rm -f "${dirName}" + fi +} + +# Check Directory exist in the path +checkDirExists () { + local directoryPath="$1" + + [[ -d "${directoryPath}" ]] && echo -n "true" || echo -n "false" +} + + +# Utility method to create a file +# Failure conditions: +# Parameters: +## $1: file to create +# Depends on global: none +# Updates global: none +# Returns: NA + +createFile(){ + local fileName="$1" + logSilly "Method ${FUNCNAME[0]} [$fileName]" + [ -f "${fileName}" ] && return 0 + touch "${fileName}" || return 1 + + local userID="$2" + local groupID=${3:-$userID} + + # If UID/GID is passed, chown the folder + if [ ! -z "$userID" ] && [ ! -z "$groupID" ]; then + io_setOwnership "$fileName" "$userID" "$groupID" || return 1 + fi +} + +# Check File exist in the filePath +# IMPORTANT- DON'T ADD LOGGING to this method +checkFileExists () { + local filePath="$1" + + [[ -f "${filePath}" ]] && echo -n "true" || echo -n "false" +} + +# Check for directories contains any (files or sub directories) +# IMPORTANT- DON'T ADD LOGGING to this method +checkDirContents () { + local directoryPath="$1" + if [[ "$(ls -1 "${directoryPath}" | wc -l)" -gt 0 ]]; then + echo -n "true" + else + echo -n "false" + fi +} + +# Check contents exist in directory +# IMPORTANT- DON'T ADD LOGGING to this method +checkContentExists () { + local source="$1" + + if [[ "$(checkDirContents "${source}")" != "true" ]]; then + echo -n "false" + else + echo -n "true" + fi +} + +# Resolve the variable +# IMPORTANT- DON'T ADD LOGGING to this method +evalVariable () { + local output="$1" + local input="$2" + + eval "${output}"=\${"${input}"} + eval echo \${"${output}"} +} + +# Usage: if [ "$(io_commandExists 'curl')" == "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_commandExists() { + local commandToExecute="$1" + hash "${commandToExecute}" 2>/dev/null + local rt=$? + if [ "$rt" == 0 ]; then echo -n "yes"; else echo -n "no"; fi +} + +# Usage: if [ "$(io_curlExists)" != "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_curlExists() { + io_commandExists "curl" +} + + +io_hasMatch() { + logSilly "Method ${FUNCNAME[0]}" + local result=0 + logDebug "Executing [echo \"$1\" | grep \"$2\" >/dev/null 2>&1]" + echo "$1" | grep "$2" >/dev/null 2>&1 || result=1 + return $result +} + +# Utility method to check if the string passed (usually a connection url) corresponds to this machine itself +# Failure conditions: None +# Parameters: +## $1: string to check against +# Depends on global: none +# Updates global: IS_LOCALHOST with value "yes/no" +# Returns: NA + +io_getIsLocalhost() { + logSilly "Method ${FUNCNAME[0]}" + IS_LOCALHOST="$FLAG_N" + local inputString="$1" + logDebug "Parsing [$inputString] to check if we are dealing with this machine itself" + + io_hasMatch "$inputString" "localhost" && { + logDebug "Found localhost. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for localhost" + + local hostIP=$(io_getPublicHostIP) + io_hasMatch "$inputString" "$hostIP" && { + logDebug "Found $hostIP. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostIP" + + local hostID=$(io_getPublicHostID) + io_hasMatch "$inputString" "$hostID" && { + logDebug "Found $hostID. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostID" + + local hostName=$(io_getPublicHostName) + io_hasMatch "$inputString" "$hostName" && { + logDebug "Found $hostName. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostName" + +} + +# Usage: if [ "$(io_tarExists)" != "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_tarExists() { + io_commandExists "tar" +} + +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostIP() { + local OS_TYPE=$(uname) + local publicHostIP= + if [ "${OS_TYPE}" == "Darwin" ]; then + ipStatus=$(ifconfig en0 | grep "status" | awk '{print$2}') + if [ "${ipStatus}" == "active" ]; then + publicHostIP=$(ifconfig en0 | grep inet | grep -v inet6 | awk '{print $2}') + else + errorExit "Host IP could not be resolved!" + fi + elif [ "${OS_TYPE}" == "Linux" ]; then + publicHostIP=$(hostname -i 2>/dev/null || echo "127.0.0.1") + fi + publicHostIP=$(echo "${publicHostIP}" | awk '{print $1}') + echo -n "${publicHostIP}" +} + +# Will return the short host name (up to the first dot) +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostName() { + echo -n "$(hostname -s)" +} + +# Will return the full host name (use this as much as possible) +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostID() { + echo -n "$(hostname)" +} + +# Utility method to backup a file +# Failure conditions: NA +# Parameters: filePath +# Depends on global: none, +# Updates global: none +# Returns: NA +io_backupFile() { + logSilly "Method ${FUNCNAME[0]}" + fileName="$1" + if [ ! -f "${filePath}" ]; then + logDebug "No file: [${filePath}] to backup" + return + fi + dateTime=$(date +"%Y-%m-%d-%H-%M-%S") + targetFileName="${fileName}.backup.${dateTime}" + yes | \cp -f "$fileName" "${targetFileName}" + logger "File [${fileName}] backedup as [${targetFileName}]" +} + +# Reference https://stackoverflow.com/questions/4023830/how-to-compare-two-strings-in-dot-separated-version-format-in-bash/4025065#4025065 +is_number() { + case "$BASH_VERSION" in + 3.1.*) + PATTERN='\^\[0-9\]+\$' + ;; + *) + PATTERN='^[0-9]+$' + ;; + esac + + [[ "$1" =~ $PATTERN ]] +} + +io_compareVersions() { + if [[ $# != 2 ]] + then + echo "Usage: min_version current minimum" + return + fi + + A="${1%%.*}" + B="${2%%.*}" + + if [[ "$A" != "$1" && "$B" != "$2" && "$A" == "$B" ]] + then + io_compareVersions "${1#*.}" "${2#*.}" + else + if is_number "$A" && is_number "$B" + then + if [[ "$A" -eq "$B" ]]; then + echo "0" + elif [[ "$A" -gt "$B" ]]; then + echo "1" + elif [[ "$A" -lt "$B" ]]; then + echo "-1" + fi + fi + fi +} + +# Reference https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-a-bash-variable +# Strip all leading and trailing spaces +# IMPORTANT- DON'T ADD LOGGING to this method +io_trim() { + local var="$1" + # remove leading whitespace characters + var="${var#"${var%%[![:space:]]*}"}" + # remove trailing whitespace characters + var="${var%"${var##*[![:space:]]}"}" + echo -n "$var" +} + +# temporary function will be removing it ASAP +# search for string and replace text in file +replaceText_migration_hook () { + local regexString="$1" + local replaceText="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e "s/${regexString}/${replaceText}/" "${file}" || warn "Failed to replace the text in ${file}" + else + sed -i -e "s/${regexString}/${replaceText}/" "${file}" || warn "Failed to replace the text in ${file}" + fi +} + +# search for string and replace text in file +replaceText () { + local regexString="$1" + local replaceText="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e "s#${regexString}#${replaceText}#" "${file}" || warn "Failed to replace the text in ${file}" + else + sed -i -e "s#${regexString}#${replaceText}#" "${file}" || warn "Failed to replace the text in ${file}" + logDebug "Replaced [$regexString] with [$replaceText] in [$file]" + fi +} + +# search for string and prepend text in file +prependText () { + local regexString="$1" + local text="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e '/'"${regexString}"'/i\'$'\n\\'"${text}"''$'\n' "${file}" || warn "Failed to prepend the text in ${file}" + else + sed -i -e '/'"${regexString}"'/i\'$'\n\\'"${text}"''$'\n' "${file}" || warn "Failed to prepend the text in ${file}" + fi +} + +# add text to beginning of the file +addText () { + local text="$1" + local file="$2" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e '1s/^/'"${text}"'\'$'\n/' "${file}" || warn "Failed to add the text in ${file}" + else + sed -i -e '1s/^/'"${text}"'\'$'\n/' "${file}" || warn "Failed to add the text in ${file}" + fi +} + +io_replaceString () { + local value="$1" + local firstString="$2" + local secondString="$3" + local separator=${4:-"/"} + local updateValue= + if [[ $(uname) == "Darwin" ]]; then + updateValue=$(echo "${value}" | sed "s${separator}${firstString}${separator}${secondString}${separator}") + else + updateValue=$(echo "${value}" | sed "s${separator}${firstString}${separator}${secondString}${separator}") + fi + echo -n "${updateValue}" +} + +_findYQ() { + # logSilly "Method ${FUNCNAME[0]}" (Intentionally not logging. Does not add value) + local parentDir="$1" + if [ -z "$parentDir" ]; then + return + fi + logDebug "Executing command [find "${parentDir}" -name third-party -type d]" + local yq=$(find "${parentDir}" -name third-party -type d) + if [ -d "${yq}/yq" ]; then + export YQ_PATH="${yq}/yq" + fi +} + + +io_setYQPath() { + # logSilly "Method ${FUNCNAME[0]}" (Intentionally not logging. Does not add value) + if [ "$(io_commandExists 'yq')" == "yes" ]; then + return + fi + + if [ ! -z "${JF_PRODUCT_HOME}" ] && [ -d "${JF_PRODUCT_HOME}" ]; then + _findYQ "${JF_PRODUCT_HOME}" + fi + + if [ -z "${YQ_PATH}" ] && [ ! -z "${COMPOSE_HOME}" ] && [ -d "${COMPOSE_HOME}" ]; then + _findYQ "${COMPOSE_HOME}" + fi + # TODO We can remove this block after all the code is restructured. + if [ -z "${YQ_PATH}" ] && [ ! -z "${SCRIPT_HOME}" ] && [ -d "${SCRIPT_HOME}" ]; then + _findYQ "${SCRIPT_HOME}" + fi + +} + +io_getLinuxDistribution() { + LINUX_DISTRIBUTION= + + # Make sure running on Linux + [ $(uname -s) != "Linux" ] && return + + # Find out what Linux distribution we are on + + cat /etc/*-release | grep -i Red >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat || true + + # OS 6.x + cat /etc/issue.net | grep Red >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat || true + + # OS 7.x + cat /etc/*-release | grep -i centos >/dev/null 2>&1 && LINUX_DISTRIBUTION=CentOS && LINUX_DISTRIBUTION_VER="7" || true + + # OS 8.x + grep -q -i "release 8" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="8" || true + + # OS 7.x + grep -q -i "release 7" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="7" || true + + # OS 6.x + grep -q -i "release 6" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="6" || true + + cat /etc/*-release | grep -i Red | grep -i 'VERSION=7' >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat && LINUX_DISTRIBUTION_VER="7" || true + + cat /etc/*-release | grep -i debian >/dev/null 2>&1 && LINUX_DISTRIBUTION=Debian || true + + cat /etc/*-release | grep -i ubuntu >/dev/null 2>&1 && LINUX_DISTRIBUTION=Ubuntu || true +} + +## Utility method to check ownership of folders/files +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If file is not owned by the user & group +## Parameters: + ## user + ## group + ## folder to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac +io_checkOwner () { + logSilly "Method ${FUNCNAME[0]}" + local osType=$(uname) + + if [ "${osType}" != "Linux" ]; then + logDebug "Unsupported OS. Skipping check" + return 0 + fi + + local file_to_check=$1 + local user_id_to_check=$2 + + + if [ -z "$user_id_to_check" ] || [ -z "$file_to_check" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group_id_to_check=${3:-$user_id_to_check} + local check_user_name=${4:-"no"} + + logDebug "Checking permissions on [$file_to_check] for user [$user_id_to_check] & group [$group_id_to_check]" + + local stat= + + if [ "${check_user_name}" == "yes" ]; then + stat=( $(stat -Lc "%U %G" ${file_to_check}) ) + else + stat=( $(stat -Lc "%u %g" ${file_to_check}) ) + fi + + local user_id=${stat[0]} + local group_id=${stat[1]} + + if [[ "${user_id}" != "${user_id_to_check}" ]] || [[ "${group_id}" != "${group_id_to_check}" ]] ; then + logDebug "Ownership mismatch. [${file_to_check}] is not owned by [${user_id_to_check}:${group_id_to_check}]" + return 1 + else + return 0 + fi +} + +## Utility method to change ownership of a file/folder - NON recursive +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If chown operation fails - returns 1 +## Parameters: + ## user + ## group + ## file to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac + +io_setOwnershipNonRecursive() { + + local osType=$(uname) + if [ "${osType}" != "Linux" ]; then + return + fi + + local targetFile=$1 + local user=$2 + + if [ -z "$user" ] || [ -z "$targetFile" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group=${3:-$user} + logDebug "Method ${FUNCNAME[0]}. Executing [chown ${user}:${group} ${targetFile}]" + chown ${user}:${group} ${targetFile} || return 1 +} + +## Utility method to change ownership of a file. +## IMPORTANT +## If being called on a folder, should ONLY be called for fresh folders or may cause performance issues +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If chown operation fails - returns 1 +## Parameters: + ## user + ## group + ## file to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac + +io_setOwnership() { + + local osType=$(uname) + if [ "${osType}" != "Linux" ]; then + return + fi + + local targetFile=$1 + local user=$2 + + if [ -z "$user" ] || [ -z "$targetFile" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group=${3:-$user} + logDebug "Method ${FUNCNAME[0]}. Executing [chown -R ${user}:${group} ${targetFile}]" + chown -R ${user}:${group} ${targetFile} || return 1 +} + +## Utility method to create third party folder structure necessary for Postgres +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## POSTGRESQL_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createPostgresDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${POSTGRESQL_DATA_ROOT}" ] && return 0 + + logDebug "Property [${POSTGRESQL_DATA_ROOT}] exists. Proceeding" + + createDir "${POSTGRESQL_DATA_ROOT}/data" + io_setOwnership "${POSTGRESQL_DATA_ROOT}" "${POSTGRES_USER}" "${POSTGRES_USER}" || errorExit "Setting ownership of [${POSTGRESQL_DATA_ROOT}] to [${POSTGRES_USER}:${POSTGRES_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Nginx +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## NGINX_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createNginxDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${NGINX_DATA_ROOT}" ] && return 0 + + logDebug "Property [${NGINX_DATA_ROOT}] exists. Proceeding" + + createDir "${NGINX_DATA_ROOT}" + io_setOwnership "${NGINX_DATA_ROOT}" "${NGINX_USER}" "${NGINX_GROUP}" || errorExit "Setting ownership of [${NGINX_DATA_ROOT}] to [${NGINX_USER}:${NGINX_GROUP}] failed" +} + +## Utility method to create third party folder structure necessary for ElasticSearch +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## ELASTIC_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createElasticSearchDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${ELASTIC_DATA_ROOT}" ] && return 0 + + logDebug "Property [${ELASTIC_DATA_ROOT}] exists. Proceeding" + + createDir "${ELASTIC_DATA_ROOT}/data" + io_setOwnership "${ELASTIC_DATA_ROOT}" "${ES_USER}" "${ES_USER}" || errorExit "Setting ownership of [${ELASTIC_DATA_ROOT}] to [${ES_USER}:${ES_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Redis +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## REDIS_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createRedisDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${REDIS_DATA_ROOT}" ] && return 0 + + logDebug "Property [${REDIS_DATA_ROOT}] exists. Proceeding" + + createDir "${REDIS_DATA_ROOT}" + io_setOwnership "${REDIS_DATA_ROOT}" "${REDIS_USER}" "${REDIS_USER}" || errorExit "Setting ownership of [${REDIS_DATA_ROOT}] to [${REDIS_USER}:${REDIS_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Mongo +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## MONGODB_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createMongoDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${MONGODB_DATA_ROOT}" ] && return 0 + + logDebug "Property [${MONGODB_DATA_ROOT}] exists. Proceeding" + + createDir "${MONGODB_DATA_ROOT}/logs" + createDir "${MONGODB_DATA_ROOT}/configdb" + createDir "${MONGODB_DATA_ROOT}/db" + io_setOwnership "${MONGODB_DATA_ROOT}" "${MONGO_USER}" "${MONGO_USER}" || errorExit "Setting ownership of [${MONGODB_DATA_ROOT}] to [${MONGO_USER}:${MONGO_USER}] failed" +} + +## Utility method to create third party folder structure necessary for RabbitMQ +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## RABBITMQ_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createRabbitMQDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${RABBITMQ_DATA_ROOT}" ] && return 0 + + logDebug "Property [${RABBITMQ_DATA_ROOT}] exists. Proceeding" + + createDir "${RABBITMQ_DATA_ROOT}" + io_setOwnership "${RABBITMQ_DATA_ROOT}" "${RABBITMQ_USER}" "${RABBITMQ_USER}" || errorExit "Setting ownership of [${RABBITMQ_DATA_ROOT}] to [${RABBITMQ_USER}:${RABBITMQ_USER}] failed" +} + +# Add or replace a property in provided properties file +addOrReplaceProperty() { + local propertyName=$1 + local propertyValue=$2 + local propertiesPath=$3 + local delimiter=${4:-"="} + + # Return if any of the inputs are empty + [[ -z "$propertyName" || "$propertyName" == "" ]] && return + [[ -z "$propertyValue" || "$propertyValue" == "" ]] && return + [[ -z "$propertiesPath" || "$propertiesPath" == "" ]] && return + + grep "^${propertyName}\s*${delimiter}.*$" ${propertiesPath} > /dev/null 2>&1 + [ $? -ne 0 ] && echo -e "\n${propertyName}${delimiter}${propertyValue}" >> ${propertiesPath} + sed -i -e "s|^${propertyName}\s*${delimiter}.*$|${propertyName}${delimiter}${propertyValue}|g;" ${propertiesPath} +} + +# Set property only if its not set +io_setPropertyNoOverride(){ + local propertyName=$1 + local propertyValue=$2 + local propertiesPath=$3 + + # Return if any of the inputs are empty + [[ -z "$propertyName" || "$propertyName" == "" ]] && return + [[ -z "$propertyValue" || "$propertyValue" == "" ]] && return + [[ -z "$propertiesPath" || "$propertiesPath" == "" ]] && return + + grep "^${propertyName}:" ${propertiesPath} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo -e "${propertyName}: ${propertyValue}" >> ${propertiesPath} || warn "Setting property ${propertyName}: ${propertyValue} in [ ${propertiesPath} ] failed" + else + logger "Skipping update of property : ${propertyName}" >&6 + fi +} + +# Add a line to a file if it doesn't already exist +addLine() { + local line_to_add=$1 + local target_file=$2 + logger "Trying to add line $1 to $2" >&6 2>&1 + cat "$target_file" | grep -F "$line_to_add" -wq >&6 2>&1 + if [ $? != 0 ]; then + logger "Line does not exist and will be added" >&6 2>&1 + echo $line_to_add >> $target_file || errorExit "Could not update $target_file" + fi +} + +# Utility method to check if a value (first parameter) exists in an array (2nd parameter) +# 1st parameter "value to find" +# 2nd parameter "The array to search in. Please pass a string with each value separated by space" +# Example: containsElement "y" "y Y n N" +containsElement () { + local searchElement=$1 + local searchArray=($2) + local found=1 + for elementInIndex in "${searchArray[@]}";do + if [[ $elementInIndex == $searchElement ]]; then + found=0 + fi + done + return $found +} + +# Utility method to get user's choice +# 1st parameter "what to ask the user" +# 2nd parameter "what choices to accept, separated by spaces" +# 3rd parameter "what is the default choice (to use if the user simply presses Enter)" +# Example 'getUserChoice "Are you feeling lucky? Punk!" "y n Y N" "y"' +getUserChoice(){ + configureLogOutput + read_timeout=${read_timeout:-0.5} + local choice="na" + local text_to_display=$1 + local choices=$2 + local default_choice=$3 + users_choice= + + until containsElement "$choice" "$choices"; do + echo "";echo ""; + sleep $read_timeout #This ensures correct placement of the question. + read -p "$text_to_display :" choice + : ${choice:=$default_choice} + done + users_choice=$choice + echo -e "\n$text_to_display: $users_choice" >&6 + sleep $read_timeout #This ensures correct logging +} + +setFilePermission () { + local permission=$1 + local file=$2 + chmod "${permission}" "${file}" || warn "Setting permission ${permission} to file [ ${file} ] failed" +} + + +#setting required paths +setAppDir (){ + SCRIPT_DIR=$(dirname $0) + SCRIPT_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + APP_DIR="`cd "${SCRIPT_HOME}";pwd`" +} + +ZIP_TYPE="zip" +COMPOSE_TYPE="compose" +HELM_TYPE="helm" +RPM_TYPE="rpm" +DEB_TYPE="debian" + +sourceScript () { + local file="$1" + + [ ! -z "${file}" ] || errorExit "target file is not passed to source a file" + + if [ ! -f "${file}" ]; then + errorExit "${file} file is not found" + else + source "${file}" || errorExit "Unable to source ${file}, please check if the user ${USER} has permissions to perform this action" + fi +} +# Source required helpers +initHelpers () { + local systemYamlHelper="${APP_DIR}/systemYamlHelper.sh" + local thirdPartyDir=$(find ${APP_DIR}/.. -name third-party -type d) + export YQ_PATH="${thirdPartyDir}/yq" + LIBXML2_PATH="${thirdPartyDir}/libxml2/bin/xmllint" + export LD_LIBRARY_PATH="${thirdPartyDir}/libxml2/lib" + sourceScript "${systemYamlHelper}" +} +# Check migration info yaml file available in the path +checkMigrationInfoYaml () { + + if [[ -f "${APP_DIR}/migrationHelmInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationHelmInfo.yaml" + INSTALLER="${HELM_TYPE}" + elif [[ -f "${APP_DIR}/migrationZipInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationZipInfo.yaml" + INSTALLER="${ZIP_TYPE}" + elif [[ -f "${APP_DIR}/migrationRpmInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationRpmInfo.yaml" + INSTALLER="${RPM_TYPE}" + elif [[ -f "${APP_DIR}/migrationDebInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationDebInfo.yaml" + INSTALLER="${DEB_TYPE}" + elif [[ -f "${APP_DIR}/migrationComposeInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationComposeInfo.yaml" + INSTALLER="${COMPOSE_TYPE}" + else + errorExit "File migration Info yaml does not exist in [${APP_DIR}]" + fi +} + +retrieveYamlValue () { + local yamlPath="$1" + local value="$2" + local output="$3" + local message="$4" + + [[ -z "${yamlPath}" ]] && errorExit "yamlPath is mandatory to get value from ${MIGRATION_SYSTEM_YAML_INFO}" + + getYamlValue "${yamlPath}" "${MIGRATION_SYSTEM_YAML_INFO}" "false" + value="${YAML_VALUE}" + if [[ -z "${value}" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "Empty value for ${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + elif [[ "${output}" == "Skip" ]]; then + return + else + errorExit "${message}" + fi + fi +} + +checkEnv () { + + if [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + # check Environment JF_PRODUCT_HOME is set before migration + NEW_DATA_DIR="$(evalVariable "NEW_DATA_DIR" "JF_PRODUCT_HOME")" + if [[ -z "${NEW_DATA_DIR}" ]]; then + errorExit "Environment variable JF_PRODUCT_HOME is not set, this is required to perform Migration" + fi + # appending var directory to $JF_PRODUCT_HOME + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + elif [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + getCustomDataDir_hook + NEW_DATA_DIR="${OLD_DATA_DIR}" + if [[ -z "${NEW_DATA_DIR}" ]] && [[ -z "${OLD_DATA_DIR}" ]]; then + errorExit "Could not find ${PROMPT_DATA_DIR_LOCATION} to perform Migration" + fi + else + # check Environment JF_ROOT_DATA_DIR is set before migration + OLD_DATA_DIR="$(evalVariable "OLD_DATA_DIR" "JF_ROOT_DATA_DIR")" + # check Environment JF_ROOT_DATA_DIR is set before migration + NEW_DATA_DIR="$(evalVariable "NEW_DATA_DIR" "JF_ROOT_DATA_DIR")" + if [[ -z "${NEW_DATA_DIR}" ]] && [[ -z "${OLD_DATA_DIR}" ]]; then + errorExit "Could not find ${PROMPT_DATA_DIR_LOCATION} to perform Migration" + fi + # appending var directory to $JF_PRODUCT_HOME + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + fi + +} + +getDataDir () { + + if [[ "${INSTALLER}" == "${ZIP_TYPE}" || "${INSTALLER}" == "${COMPOSE_TYPE}"|| "${INSTALLER}" == "${HELM_TYPE}" ]]; then + checkEnv + else + getCustomDataDir_hook + NEW_DATA_DIR="`cd "${APP_DIR}"/../../;pwd`" + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + fi +} + +# Retrieve Product name from MIGRATION_SYSTEM_YAML_INFO +getProduct () { + retrieveYamlValue "migration.product" "${YAML_VALUE}" "Fail" "Empty value under ${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + PRODUCT="${YAML_VALUE}" + PRODUCT=$(echo "${PRODUCT}" | tr '[:upper:]' '[:lower:]' 2>/dev/null) + if [[ "${PRODUCT}" != "artifactory" && "${PRODUCT}" != "distribution" && "${PRODUCT}" != "xray" ]]; then + errorExit "migration.product in [${MIGRATION_SYSTEM_YAML_INFO}] is not correct, please set based on product as ARTIFACTORY or DISTRIBUTION" + fi + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + JF_USER="${PRODUCT}" + fi +} +# Compare product version with minProductVersion and maxProductVersion +migrateCheckVersion () { + local productVersion="$1" + local minProductVersion="$2" + local maxProductVersion="$3" + local productVersion618="6.18.0" + local unSupportedProductVersions7=("7.2.0 7.2.1") + + if [[ "$(io_compareVersions "${productVersion}" "${maxProductVersion}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${maxProductVersion}")" -eq 1 ]]; then + logger "Migration not necessary. ${PRODUCT} is already ${productVersion}" + exit 11 + elif [[ "$(io_compareVersions "${productVersion}" "${minProductVersion}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${minProductVersion}")" -eq 1 ]]; then + if [[ ("$(io_compareVersions "${productVersion}" "${productVersion618}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${productVersion618}")" -eq 1) && " ${unSupportedProductVersions7[@]} " =~ " ${CURRENT_VERSION} " ]]; then + touch /tmp/error; + errorExit "Current ${PRODUCT} version (${productVersion}) does not support migration to ${CURRENT_VERSION}" + else + bannerStart "Detected ${PRODUCT} ${productVersion}, initiating migration" + fi + else + logger "Current ${PRODUCT} ${productVersion} version is not supported for migration" + exit 1 + fi +} + +getProductVersion () { + local minProductVersion="$1" + local maxProductVersion="$2" + local newfilePath="$3" + local oldfilePath="$4" + local propertyInDocker="$5" + local property="$6" + local productVersion= + local status= + + if [[ "$INSTALLER" == "${COMPOSE_TYPE}" ]]; then + if [[ -f "${oldfilePath}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + else + productVersion="$(cat "${oldfilePath}")" + fi + status="success" + elif [[ -f "${newfilePath}" ]]; then + productVersion="$(readKey "${propertyInDocker}" "${newfilePath}")" + status="fail" + else + logger "File [${oldfilePath}] or [${newfilePath}] not found to get current version." + exit 0 + fi + elif [[ "$INSTALLER" == "${HELM_TYPE}" ]]; then + if [[ -f "${oldfilePath}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + else + productVersion="$(cat "${oldfilePath}")" + fi + status="success" + else + productVersion="${CURRENT_VERSION}" + [[ -z "${productVersion}" || "${productVersion}" == "" ]] && logger "${PRODUCT} CURRENT_VERSION is not set" && exit 0 + fi + else + if [[ -f "${newfilePath}" ]]; then + productVersion="$(readKey "${property}" "${newfilePath}")" + status="fail" + elif [[ -f "${oldfilePath}" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + status="success" + else + if [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + logger "File [${newfilePath}] not found to get current version." + else + logger "File [${oldfilePath}] or [${newfilePath}] not found to get current version." + fi + exit 0 + fi + fi + if [[ -z "${productVersion}" || "${productVersion}" == "" ]]; then + [[ "${status}" == "success" ]] && logger "No version found in file [${oldfilePath}]." + [[ "${status}" == "fail" ]] && logger "No version found in file [${newfilePath}]." + exit 0 + fi + + migrateCheckVersion "${productVersion}" "${minProductVersion}" "${maxProductVersion}" +} + +readKey () { + local property="$1" + local file="$2" + local version= + + while IFS='=' read -r key value || [ -n "${key}" ]; + do + [[ ! "${key}" =~ \#.* && ! -z "${key}" && ! -z "${value}" ]] + key="$(io_trim "${key}")" + if [[ "${key}" == "${property}" ]]; then + version="${value}" && check=true && break + else + check=false + fi + done < "${file}" + if [[ "${check}" == "false" ]]; then + return + fi + echo "${version}" +} + +# create Log directory +createLogDir () { + if [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" + fi +} + +# Creating migration log file +creationMigrateLog () { + local LOG_FILE_NAME="migration.log" + createLogDir + local MIGRATION_LOG_FILE="${NEW_DATA_DIR}/log/${LOG_FILE_NAME}" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + MIGRATION_LOG_FILE="${SCRIPT_HOME}/${LOG_FILE_NAME}" + fi + touch "${MIGRATION_LOG_FILE}" + setFilePermission "${LOG_FILE_PERMISSION}" "${MIGRATION_LOG_FILE}" + exec &> >(tee -a "${MIGRATION_LOG_FILE}") +} +# Set path where system.yaml should create +setSystemYamlPath () { + SYSTEM_YAML_PATH="${NEW_DATA_DIR}/etc/system.yaml" + if [[ "${INSTALLER}" != "${HELM_TYPE}" ]]; then + logger "system.yaml will be created in path [${SYSTEM_YAML_PATH}]" + fi +} +# Create directory +createDirectory () { + local directory="$1" + local output="$2" + local check=false + local message="Could not create directory ${directory}, please check if the user ${USER} has permissions to perform this action" + removeSoftLink "${directory}" + mkdir -p "${directory}" && check=true || check=false + if [[ "${check}" == "false" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "${message}" + else + errorExit "${message}" + fi + fi + setOwnershipBasedOnInstaller "${directory}" +} + +setOwnershipBasedOnInstaller () { + local directory="$1" + if [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + chown -R ${USER_TO_CHECK}:${GROUP_TO_CHECK} "${directory}" || warn "Setting ownership on $directory failed" + elif [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + io_setOwnership "${directory}" "${JF_USER}" "${JF_USER}" + fi +} + +getUserAndGroup () { + local file="$1" + read uid gid <<<$(stat -c '%U %G' ${file}) + USER_TO_CHECK="${uid}" + GROUP_TO_CHECK="${gid}" +} + +# set ownership +getUserAndGroupFromFile () { + case $PRODUCT in + artifactory) + getUserAndGroup "/etc/opt/jfrog/artifactory/artifactory.properties" + ;; + distribution) + getUserAndGroup "${OLD_DATA_DIR}/etc/versions.properties" + ;; + xray) + getUserAndGroup "${OLD_DATA_DIR}/security/master.key" + ;; + esac +} + +# creating required directories +createRequiredDirs () { + bannerSubSection "CREATING REQUIRED DIRECTORIES" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc/security" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log/archived" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/work" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/backup" "${JF_USER}" "${JF_USER}" "yes" + io_setOwnership "${NEW_DATA_DIR}" "${JF_USER}" "${JF_USER}" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data/postgres" "${POSTGRES_USER}" "${POSTGRES_USER}" "yes" + fi + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc/security" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log/archived" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/work" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/backup" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + fi +} + +# Check entry in map is format +checkMapEntry () { + local entry="$1" + + [[ "${entry}" != *"="* ]] && echo -n "false" || echo -n "true" +} +# Check value Empty and warn +warnIfEmpty () { + local filePath="$1" + local yamlPath="$2" + local check= + + if [[ -z "${filePath}" ]]; then + warn "Empty value in yamlpath [${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + check=false + else + check=true + fi + echo "${check}" +} + +logCopyStatus () { + local status="$1" + local logMessage="$2" + local warnMessage="$3" + + [[ "${status}" == "success" ]] && logger "${logMessage}" + [[ "${status}" == "fail" ]] && warn "${warnMessage}" +} +# copy contents from source to destination +copyCmd () { + local source="$1" + local target="$2" + local mode="$3" + local status= + + case $mode in + unique) + cp -up "${source}"/* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied directory contents from [${source}] to [${target}]" "Failed to copy directory contents from [${source}] to [${target}]" + ;; + specific) + cp -pf "${source}" "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied file [${source}] to [${target}]" "Failed to copy file [${source}] to [${target}]" + ;; + patternFiles) + cp -pf "${source}"* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied files matching [${source}*] to [${target}]" "Failed to copy files matching [${source}*] to [${target}]" + ;; + full) + cp -prf "${source}"/* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied directory contents from [${source}] to [${target}]" "Failed to copy directory contents from [${source}] to [${target}]" + ;; + esac +} +# Check contents exist in source before copying +copyOnContentExist () { + local source="$1" + local target="$2" + local mode="$3" + + if [[ "$(checkContentExists "${source}")" == "true" ]]; then + copyCmd "${source}" "${target}" "${mode}" + else + logger "No contents to copy from [${source}]" + fi +} + +# move source to destination +moveCmd () { + local source="$1" + local target="$2" + local status= + + mv -f "${source}" "${target}" && status="success" || status="fail" + [[ "${status}" == "success" ]] && logger "Successfully moved directory [${source}] to [${target}]" + [[ "${status}" == "fail" ]] && warn "Failed to move directory [${source}] to [${target}]" +} + +# symlink target to source +symlinkCmd () { + local source="$1" + local target="$2" + local symlinkSubDir="$3" + local check=false + + if [[ "${symlinkSubDir}" == "subDir" ]]; then + ln -sf "${source}"/* "${target}" && check=true || check=false + else + ln -sf "${source}" "${target}" && check=true || check=false + fi + + [[ "${check}" == "true" ]] && logger "Successfully symlinked directory [${target}] to old [${source}]" + [[ "${check}" == "false" ]] && warn "Symlink operation failed" +} +# Check contents exist in source before symlinking +symlinkOnExist () { + local source="$1" + local target="$2" + local symlinkSubDir="$3" + + if [[ "$(checkContentExists "${source}")" == "true" ]]; then + if [[ "${symlinkSubDir}" == "subDir" ]]; then + symlinkCmd "${source}" "${target}" "subDir" + else + symlinkCmd "${source}" "${target}" + fi + else + logger "No contents to symlink from [${source}]" + fi +} + +prependDir () { + local absolutePath="$1" + local fullPath="$2" + local sourcePath= + + if [[ "${absolutePath}" = \/* ]]; then + sourcePath="${absolutePath}" + else + sourcePath="${fullPath}" + fi + echo "${sourcePath}" +} + +getFirstEntry (){ + local entry="$1" + + [[ -z "${entry}" ]] && return + echo "${entry}" | awk -F"=" '{print $1}' +} + +getSecondEntry () { + local entry="$1" + + [[ -z "${entry}" ]] && return + echo "${entry}" | awk -F"=" '{print $2}' +} +# To get absolutePath +pathResolver () { + local directoryPath="$1" + local dataDir= + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + retrieveYamlValue "migration.oldDataDir" "oldDataDir" "Warning" + dataDir="${YAML_VALUE}" + cd "${dataDir}" + else + cd "${OLD_DATA_DIR}" + fi + absoluteDir="`cd "${directoryPath}";pwd`" + echo "${absoluteDir}" +} + +checkPathResolver () { + local value="$1" + + if [[ "${value}" == \/* ]]; then + value="${value}" + else + value="$(pathResolver "${value}")" + fi + echo "${value}" +} + +propertyMigrate () { + local entry="$1" + local filePath="$2" + local fileName="$3" + local check=false + + local yamlPath="$(getFirstEntry "${entry}")" + local property="$(getSecondEntry "${entry}")" + if [[ -z "${property}" ]]; then + warn "Property is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${property}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + local keyValues=$(cat "${NEW_DATA_DIR}/${filePath}/${fileName}" | grep "^[^#]" | grep "[*=*]") + for i in ${keyValues}; do + key=$(echo "${i}" | awk -F"=" '{print $1}') + value=$(echo "${i}" | cut -f 2- -d '=') + [ -z "${key}" ] && continue + [ -z "${value}" ] && continue + if [[ "${key}" == "${property}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + value="$(migrateResolveDerbyPath "${key}" "${value}")" + value="$(migrateResolveHaDirPath "${key}" "${value}")" + if [[ "${INSTALLER}" != "${DOCKER_TYPE}" ]]; then + value="$(updatePostgresUrlString_Hook "${yamlPath}" "${value}")" + fi + fi + if [[ "${key}" == "context.url" ]]; then + local ip=$(echo "${value}" | awk -F/ '{print $3}' | sed 's/:.*//') + setSystemValue "shared.node.ip" "${ip}" "${SYSTEM_YAML_PATH}" + logger "Setting [shared.node.ip] with [${ip}] in system.yaml" + fi + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" && logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" && check=true && break || check=false + fi + done + [[ "${check}" == "false" ]] && logger "Property [${property}] not found in file [${fileName}]" +} + +setHaEnabled_hook () { + echo "" +} + +migratePropertiesFiles () { + local fileList= + local filePath= + local fileName= + local map= + + retrieveYamlValue "migration.propertyFiles.files" "fileList" "Skip" + fileList="${YAML_VALUE}" + if [[ -z "${fileList}" ]]; then + return + fi + bannerSection "PROCESSING MIGRATION OF PROPERTY FILES" + for file in ${fileList}; + do + bannerSubSection "Processing Migration of $file" + retrieveYamlValue "migration.propertyFiles.$file.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + retrieveYamlValue "migration.propertyFiles.$file.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + [[ -z "${filePath}" && -z "${fileName}" ]] && continue + if [[ "$(checkFileExists "${NEW_DATA_DIR}/${filePath}/${fileName}")" == "true" ]]; then + logger "File [${fileName}] found in path [${NEW_DATA_DIR}/${filePath}]" + # setting haEnabled with true only if ha-node.properties is present + setHaEnabled_hook "${filePath}" + retrieveYamlValue "migration.propertyFiles.$file.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + propertyMigrate "${entry}" "${filePath}" "${fileName}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=property" + fi + done + else + logger "File [${fileName}] was not found in path [${NEW_DATA_DIR}/${filePath}] to migrate" + fi + done +} + +createTargetDir () { + local mountDir="$1" + local target="$2" + + logger "Target directory not found [${mountDir}/${target}], creating it" + createDirectoryRecursive "${mountDir}" "${target}" "Warning" +} + +createDirectoryRecursive () { + local mountDir="$1" + local target="$2" + local output="$3" + local check=false + local message="Could not create directory ${directory}, please check if the user ${USER} has permissions to perform this action" + removeSoftLink "${mountDir}/${target}" + local directory=$(echo "${target}" | tr '/' ' ' ) + local targetDir="${mountDir}" + for dir in ${directory}; + do + targetDir="${targetDir}/${dir}" + mkdir -p "${targetDir}" && check=true || check=false + setOwnershipBasedOnInstaller "${targetDir}" + done + if [[ "${check}" == "false" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "${message}" + else + errorExit "${message}" + fi + fi +} + +copyOperation () { + local source="$1" + local target="$2" + local mode="$3" + local check=false + local targetDataDir= + local targetLink= + local date= + + # prepend OLD_DATA_DIR only if source is relative path + source="$(prependDir "${source}" "${OLD_DATA_DIR}/${source}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + #remove source if it is a symlink + if [[ -L "${source}" ]]; then + targetLink=$(readlink -f "${source}") + logger "Removing the symlink [${source}] pointing to [${targetLink}]" + rm -f "${source}" + source=${targetLink} + fi + if [[ "$(checkDirExists "${source}")" != "true" ]]; then + logger "Source [${source}] directory not found in path" + return + fi + if [[ "$(checkDirContents "${source}")" != "true" ]]; then + logger "No contents to copy from [${source}]" + return + fi + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyOnContentExist "${source}" "${targetDataDir}/${target}" "${mode}" +} + +copySpecificFiles () { + local source="$1" + local target="$2" + local mode="$3" + + # prepend OLD_DATA_DIR only if source is relative path + source="$(prependDir "${source}" "${OLD_DATA_DIR}/${source}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + if [[ "$(checkFileExists "${source}")" != "true" ]]; then + logger "Source file [${source}] does not exist in path" + return + fi + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyCmd "${source}" "${targetDataDir}/${target}" "${mode}" +} + +copyPatternMatchingFiles () { + local source="$1" + local target="$2" + local mode="$3" + local sourcePath="${4}" + + # prepend OLD_DATA_DIR only if source is relative path + sourcePath="$(prependDir "${sourcePath}" "${OLD_DATA_DIR}/${sourcePath}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + if [[ "$(checkDirExists "${sourcePath}")" != "true" ]]; then + logger "Source [${sourcePath}] directory not found in path" + return + fi + if ls "${sourcePath}/${source}"* 1> /dev/null 2>&1; then + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyCmd "${sourcePath}/${source}" "${targetDataDir}/${target}" "${mode}" + else + logger "Source file [${sourcePath}/${source}*] does not exist in path" + fi +} + +copyLogMessage () { + local mode="$1" + case $mode in + specific) + logger "Copy file [${source}] to target [${targetDataDir}/${target}]" + ;; + patternFiles) + logger "Copy files matching [${sourcePath}/${source}*] to target [${targetDataDir}/${target}]" + ;; + full) + logger "Copy directory contents from source [${source}] to target [${targetDataDir}/${target}]" + ;; + unique) + logger "Copy directory contents from source [${source}] to target [${targetDataDir}/${target}]" + ;; + esac +} + +copyBannerMessages () { + local mode="$1" + local textMode="$2" + case $mode in + specific) + bannerSection "COPY ${textMode} FILES" + ;; + patternFiles) + bannerSection "COPY MATCHING ${textMode}" + ;; + full) + bannerSection "COPY ${textMode} DIRECTORIES CONTENTS" + ;; + unique) + bannerSection "COPY ${textMode} DIRECTORIES CONTENTS" + ;; + esac +} + +invokeCopyFunctions () { + local mode="$1" + local source="$2" + local target="$3" + + case $mode in + specific) + copySpecificFiles "${source}" "${target}" "${mode}" + ;; + patternFiles) + retrieveYamlValue "migration.${copyFormat}.sourcePath" "map" "Warning" + local sourcePath="${YAML_VALUE}" + copyPatternMatchingFiles "${source}" "${target}" "${mode}" "${sourcePath}" + ;; + full) + copyOperation "${source}" "${target}" "${mode}" + ;; + unique) + copyOperation "${source}" "${target}" "${mode}" + ;; + esac +} +# Copies contents from source directory and target directory +copyDataDirectories () { + local copyFormat="$1" + local mode="$2" + local map= + local source= + local target= + local textMode= + local targetDataDir= + local copyFormatValue= + + retrieveYamlValue "migration.${copyFormat}" "${copyFormat}" "Skip" + copyFormatValue="${YAML_VALUE}" + if [[ -z "${copyFormatValue}" ]]; then + return + fi + textMode=$(echo "${mode}" | tr '[:lower:]' '[:upper:]' 2>/dev/null) + copyBannerMessages "${mode}" "${textMode}" + retrieveYamlValue "migration.${copyFormat}.map" "map" "Warning" + map="${YAML_VALUE}" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + invokeCopyFunctions "${mode}" "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +invokeMoveFunctions () { + local source="$1" + local target="$2" + local sourceDataDir= + local targetBasename= + # prepend OLD_DATA_DIR only if source is relative path + sourceDataDir=$(prependDir "${source}" "${OLD_DATA_DIR}/${source}") + targetBasename=$(dirname "${target}") + logger "Moving directory source [${sourceDataDir}] to target [${NEW_DATA_DIR}/${target}]" + if [[ "$(checkDirExists "${sourceDataDir}")" != "true" ]]; then + logger "Directory [${sourceDataDir}] not found in path to move" + return + fi + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${targetBasename}")" != "true" ]]; then + createTargetDir "${NEW_DATA_DIR}" "${targetBasename}" + moveCmd "${sourceDataDir}" "${NEW_DATA_DIR}/${target}" + else + moveCmd "${sourceDataDir}" "${NEW_DATA_DIR}/tempDir" + moveCmd "${NEW_DATA_DIR}/tempDir" "${NEW_DATA_DIR}/${target}" + fi +} + +# Move source directory and target directory +moveDirectories () { + local moveDataDirectories= + local map= + local source= + local target= + + retrieveYamlValue "migration.moveDirectories" "moveDirectories" "Skip" + moveDirectories="${YAML_VALUE}" + if [[ -z "${moveDirectories}" ]]; then + return + fi + bannerSection "MOVE DIRECTORIES" + retrieveYamlValue "migration.moveDirectories.map" "map" "Warning" + map="${YAML_VALUE}" + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + invokeMoveFunctions "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +# Trim masterKey if its generated using hex 32 +trimMasterKey () { + local masterKeyDir=/opt/jfrog/artifactory/var/etc/security + local oldMasterKey=$(<${masterKeyDir}/master.key) + local oldMasterKey_Length=$(echo ${#oldMasterKey}) + local newMasterKey= + if [[ ${oldMasterKey_Length} -gt 32 ]]; then + bannerSection "TRIM MASTERKEY" + newMasterKey=$(echo ${oldMasterKey:0:32}) + cp ${masterKeyDir}/master.key ${masterKeyDir}/backup_master.key + logger "Original masterKey is backed up : ${masterKeyDir}/backup_master.key" + rm -rf ${masterKeyDir}/master.key + echo ${newMasterKey} > ${masterKeyDir}/master.key + logger "masterKey is trimmed : ${masterKeyDir}/master.key" + fi +} + +copyDirectories () { + + copyDataDirectories "copyFiles" "full" + copyDataDirectories "copyUniqueFiles" "unique" + copyDataDirectories "copySpecificFiles" "specific" + copyDataDirectories "copyPatternMatchingFiles" "patternFiles" +} + +symlinkDir () { + local source="$1" + local target="$2" + local targetDir= + local basename= + local targetParentDir= + + targetDir="$(dirname "${target}")" + if [[ "${targetDir}" == "${source}" ]]; then + # symlink the sub directories + createDirectory "${NEW_DATA_DIR}/${target}" "Warning" + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${target}")" == "true" ]]; then + symlinkOnExist "${OLD_DATA_DIR}/${source}" "${NEW_DATA_DIR}/${target}" "subDir" + basename="$(basename "${target}")" + cd "${NEW_DATA_DIR}/${target}" && rm -f "${basename}" + fi + else + targetParentDir="$(dirname "${NEW_DATA_DIR}/${target}")" + createDirectory "${targetParentDir}" "Warning" + if [[ "$(checkDirExists "${targetParentDir}")" == "true" ]]; then + symlinkOnExist "${OLD_DATA_DIR}/${source}" "${NEW_DATA_DIR}/${target}" + fi + fi +} + +symlinkOperation () { + local source="$1" + local target="$2" + local check=false + local targetLink= + local date= + + # Check if source is a link and do symlink + if [[ -L "${OLD_DATA_DIR}/${source}" ]]; then + targetLink=$(readlink -f "${OLD_DATA_DIR}/${source}") + symlinkOnExist "${targetLink}" "${NEW_DATA_DIR}/${target}" + else + # check if source is directory and do symlink + if [[ "$(checkDirExists "${OLD_DATA_DIR}/${source}")" != "true" ]]; then + logger "Source [${source}] directory not found in path to symlink" + return + fi + if [[ "$(checkDirContents "${OLD_DATA_DIR}/${source}")" != "true" ]]; then + logger "No contents found in [${OLD_DATA_DIR}/${source}] to symlink" + return + fi + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${target}")" != "true" ]]; then + logger "Target directory [${NEW_DATA_DIR}/${target}] does not exist to create symlink, creating it" + symlinkDir "${source}" "${target}" + else + rm -rf "${NEW_DATA_DIR}/${target}" && check=true || check=false + [[ "${check}" == "false" ]] && warn "Failed to remove contents in [${NEW_DATA_DIR}/${target}/]" + symlinkDir "${source}" "${target}" + fi + fi +} +# Creates a symlink path - Source directory to which the symbolic link should point. +symlinkDirectories () { + local linkFiles= + local map= + local source= + local target= + + retrieveYamlValue "migration.linkFiles" "linkFiles" "Skip" + linkFiles="${YAML_VALUE}" + if [[ -z "${linkFiles}" ]]; then + return + fi + bannerSection "SYMLINK DIRECTORIES" + retrieveYamlValue "migration.linkFiles.map" "map" "Warning" + map="${YAML_VALUE}" + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + logger "Symlink directory [${NEW_DATA_DIR}/${target}] to old [${OLD_DATA_DIR}/${source}]" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + symlinkOperation "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +updateConnectionString () { + local yamlPath="$1" + local value="$2" + local mongoPath="shared.mongo.url" + local rabbitmqPath="shared.rabbitMq.url" + local postgresPath="shared.database.url" + local redisPath="shared.redis.connectionString" + local mongoConnectionString="mongo.connectionString" + local sourceKey= + local hostIp=$(io_getPublicHostIP) + local hostKey= + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + # Replace @postgres:,@mongodb:,@rabbitmq:,@redis: to @{hostIp}: (Compose Installer) + hostKey="@${hostIp}:" + case $yamlPath in + ${postgresPath}) + sourceKey="@postgres:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${mongoPath}) + sourceKey="@mongodb:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${rabbitmqPath}) + sourceKey="@rabbitmq:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${redisPath}) + sourceKey="@redis:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${mongoConnectionString}) + sourceKey="@mongodb:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + esac + fi + echo -n "${value}" +} + +yamlMigrate () { + local entry="$1" + local sourceFile="$2" + local value= + local yamlPath= + local key= + yamlPath="$(getFirstEntry "${entry}")" + key="$(getSecondEntry "${entry}")" + if [[ -z "${key}" ]]; then + warn "key is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${key}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + getYamlValue "${key}" "${sourceFile}" "false" + value="${YAML_VALUE}" + if [[ ! -z "${value}" ]]; then + value=$(updateConnectionString "${yamlPath}" "${value}") + fi + if [[ -z "${value}" ]]; then + logger "No value for [${key}] in [${sourceFile}]" + else + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the key [${key}] in system.yaml" + fi +} + +migrateYamlFile () { + local files= + local filePath= + local fileName= + local sourceFile= + local map= + retrieveYamlValue "migration.yaml.files" "files" "Skip" + files="${YAML_VALUE}" + if [[ -z "${files}" ]]; then + return + fi + bannerSection "MIGRATION OF YAML FILES" + for file in $files; + do + bannerSubSection "Processing Migration of $file" + retrieveYamlValue "migration.yaml.$file.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + retrieveYamlValue "migration.yaml.$file.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + [[ -z "${filePath}" && -z "${fileName}" ]] && continue + sourceFile="${NEW_DATA_DIR}/${filePath}/${fileName}" + if [[ "$(checkFileExists "${sourceFile}")" == "true" ]]; then + logger "File [${fileName}] found in path [${NEW_DATA_DIR}/${filePath}]" + retrieveYamlValue "migration.yaml.$file.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + yamlMigrate "${entry}" "${sourceFile}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=key" + fi + done + else + logger "File [${fileName}] is not found in path [${NEW_DATA_DIR}/${filePath}] to migrate" + fi + done +} +# updates the key and value in system.yaml +updateYamlKeyValue () { + local entry="$1" + local value= + local yamlPath= + local key= + + yamlPath="$(getFirstEntry "${entry}")" + value="$(getSecondEntry "${entry}")" + if [[ -z "${value}" ]]; then + warn "value is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${key}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value [${value}] in system.yaml" +} + +updateSystemYamlFile () { + local updateYaml= + local map= + + retrieveYamlValue "migration.updateSystemYaml" "updateYaml" "Skip" + updateSystemYaml="${YAML_VALUE}" + if [[ -z "${updateSystemYaml}" ]]; then + return + fi + bannerSection "UPDATE SYSTEM YAML FILE WITH KEY AND VALUES" + retrieveYamlValue "migration.updateSystemYaml.map" "map" "Warning" + map="${YAML_VALUE}" + if [[ -z "${map}" ]]; then + return + fi + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + updateYamlKeyValue "${entry}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=key" + fi + done +} + +backupFiles_hook () { + logSilly "Method ${FUNCNAME[0]}" +} + +backupDirectory () { + local backupDir="$1" + local dir="$2" + local targetDir="$3" + local effectiveUser= + local effectiveGroup= + + if [[ "${dir}" = \/* ]]; then + dir=$(echo "${dir/\//}") + fi + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + effectiveUser="${JF_USER}" + effectiveGroup="${JF_USER}" + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + effectiveUser="${USER_TO_CHECK}" + effectiveGroup="${GROUP_TO_CHECK}" + fi + + removeSoftLinkAndCreateDir "${backupDir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local backupDirectory="${backupDir}/${PRODUCT}" + removeSoftLinkAndCreateDir "${backupDirectory}" "${effectiveUser}" "${effectiveGroup}" "yes" + removeSoftLinkAndCreateDir "${backupDirectory}/${dir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local outputCheckDirExists="$(checkDirExists "${backupDirectory}/${dir}")" + if [[ "${outputCheckDirExists}" == "true" ]]; then + copyOnContentExist "${targetDir}" "${backupDirectory}/${dir}" "full" + fi +} + +removeOldDirectory () { + local backupDir="$1" + local entry="$2" + local check=false + + # prepend OLD_DATA_DIR only if entry is relative path + local targetDir="$(prependDir "${entry}" "${OLD_DATA_DIR}/${entry}")" + local outputCheckDirExists="$(checkDirExists "${targetDir}")" + if [[ "${outputCheckDirExists}" != "true" ]]; then + logger "No [${targetDir}] directory found to delete" + echo ""; + return + fi + backupDirectory "${backupDir}" "${entry}" "${targetDir}" + rm -rf "${targetDir}" && check=true || check=false + [[ "${check}" == "true" ]] && logger "Successfully removed directory [${targetDir}]" + [[ "${check}" == "false" ]] && warn "Failed to remove directory [${targetDir}]" + echo ""; +} + +cleanUpOldDataDirectories () { + local cleanUpOldDataDir= + local map= + local entry= + + retrieveYamlValue "migration.cleanUpOldDataDir" "cleanUpOldDataDir" "Skip" + cleanUpOldDataDir="${YAML_VALUE}" + if [[ -z "${cleanUpOldDataDir}" ]]; then + return + fi + bannerSection "CLEAN UP OLD DATA DIRECTORIES" + retrieveYamlValue "migration.cleanUpOldDataDir.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + date="$(date +%Y%m%d%H%M)" + backupDir="${NEW_DATA_DIR}/backup/backup-${date}" + bannerImportant "****** Old data configurations are backedup in [${backupDir}] directory ******" + backupFiles_hook "${backupDir}/${PRODUCT}" + for entry in $map; + do + removeOldDirectory "${backupDir}" "${entry}" + done +} + +backupFiles () { + local backupDir="$1" + local dir="$2" + local targetDir="$3" + local fileName="$4" + local effectiveUser= + local effectiveGroup= + + if [[ "${dir}" = \/* ]]; then + dir=$(echo "${dir/\//}") + fi + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + effectiveUser="${JF_USER}" + effectiveGroup="${JF_USER}" + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + effectiveUser="${USER_TO_CHECK}" + effectiveGroup="${GROUP_TO_CHECK}" + fi + + removeSoftLinkAndCreateDir "${backupDir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local backupDirectory="${backupDir}/${PRODUCT}" + removeSoftLinkAndCreateDir "${backupDirectory}" "${effectiveUser}" "${effectiveGroup}" "yes" + removeSoftLinkAndCreateDir "${backupDirectory}/${dir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local outputCheckDirExists="$(checkDirExists "${backupDirectory}/${dir}")" + if [[ "${outputCheckDirExists}" == "true" ]]; then + copyCmd "${targetDir}/${fileName}" "${backupDirectory}/${dir}" "specific" + fi +} + +removeOldFiles () { + local backupDir="$1" + local directoryName="$2" + local fileName="$3" + local check=false + + # prepend OLD_DATA_DIR only if entry is relative path + local targetDir="$(prependDir "${directoryName}" "${OLD_DATA_DIR}/${directoryName}")" + local outputCheckFileExists="$(checkFileExists "${targetDir}/${fileName}")" + if [[ "${outputCheckFileExists}" != "true" ]]; then + logger "No [${targetDir}/${fileName}] file found to delete" + return + fi + backupFiles "${backupDir}" "${directoryName}" "${targetDir}" "${fileName}" + rm -f "${targetDir}/${fileName}" && check=true || check=false + [[ "${check}" == "true" ]] && logger "Successfully removed file [${targetDir}/${fileName}]" + [[ "${check}" == "false" ]] && warn "Failed to remove file [${targetDir}/${fileName}]" + echo ""; +} + +cleanUpOldFiles () { + local cleanUpFiles= + local map= + local entry= + + retrieveYamlValue "migration.cleanUpOldFiles" "cleanUpOldFiles" "Skip" + cleanUpOldFiles="${YAML_VALUE}" + if [[ -z "${cleanUpOldFiles}" ]]; then + return + fi + bannerSection "CLEAN UP OLD FILES" + retrieveYamlValue "migration.cleanUpOldFiles.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + date="$(date +%Y%m%d%H%M)" + backupDir="${NEW_DATA_DIR}/backup/backup-${date}" + bannerImportant "****** Old files are backedup in [${backupDir}] directory ******" + for entry in $map; + do + local outputCheckMapEntry="$(checkMapEntry "${entry}")" + if [[ "${outputCheckMapEntry}" != "true" ]]; then + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e directoryName=fileName" + fi + local fileName="$(getSecondEntry "${entry}")" + local directoryName="$(getFirstEntry "${entry}")" + [[ -z "${fileName}" ]] && warn "File name value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${directoryName}" ]] && warn "Directory name value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + removeOldFiles "${backupDir}" "${directoryName}" "${fileName}" + echo ""; + done +} + +startMigration () { + bannerSection "STARTING MIGRATION" +} + +endMigration () { + bannerSection "MIGRATION COMPLETED SUCCESSFULLY" +} + +initialize () { + setAppDir + _pauseExecution "setAppDir" + initHelpers + _pauseExecution "initHelpers" + checkMigrationInfoYaml + _pauseExecution "checkMigrationInfoYaml" + getProduct + _pauseExecution "getProduct" + getDataDir + _pauseExecution "getDataDir" +} + +main () { + case $PRODUCT in + artifactory) + migrateArtifactory + ;; + distribution) + migrateDistribution + ;; + xray) + migrationXray + ;; + esac + exit 0 +} + +# Ensures meta data is logged +LOG_BEHAVIOR_ADD_META="$FLAG_Y" + + +migrateResolveDerbyPath () { + local key="$1" + local value="$2" + + if [[ "${key}" == "url" && "${value}" == *"db.home"* ]]; then + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + derbyPath="/opt/jfrog/artifactory/var/data/artifactory/derby" + value=$(echo "${value}" | sed "s|{db.home}|$derbyPath|") + else + derbyPath="${NEW_DATA_DIR}/data/artifactory/derby" + value=$(echo "${value}" | sed "s|{db.home}|$derbyPath|") + fi + fi + echo "${value}" +} + +migrateResolveHaDirPath () { + local key="$1" + local value="$2" + + if [[ "${INSTALLER}" == "${RPM_TYPE}" || "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" || "${INSTALLER}" == "${DEB_TYPE}" ]]; then + if [[ "${key}" == "artifactory.ha.data.dir" || "${key}" == "artifactory.ha.backup.dir" ]]; then + value=$(checkPathResolver "${value}") + fi + fi + echo "${value}" +} +updatePostgresUrlString_Hook () { + local yamlPath="$1" + local value="$2" + local hostIp=$(io_getPublicHostIP) + local sourceKey="//postgresql:" + if [[ "${yamlPath}" == "shared.database.url" ]]; then + value=$(io_replaceString "${value}" "${sourceKey}" "//${hostIp}:" "#") + fi + echo "${value}" +} +# Check Artifactory product version +checkArtifactoryVersion () { + local minProductVersion="6.0.0" + local maxProductVersion="7.0.0" + local propertyInDocker="ARTIFACTORY_VERSION" + local property="artifactory.version" + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + local newfilePath="${APP_DIR}/../.env" + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + elif [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + elif [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + local newfilePath="${NEW_DATA_DIR}/etc/artifactory/artifactory.properties" + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + else + local newfilePath="${NEW_DATA_DIR}/etc/artifactory/artifactory.properties" + local oldfilePath="/etc/opt/jfrog/artifactory/artifactory.properties" + fi + + getProductVersion "${minProductVersion}" "${maxProductVersion}" "${newfilePath}" "${oldfilePath}" "${propertyInDocker}" "${property}" +} + +getCustomDataDir_hook () { + retrieveYamlValue "migration.oldDataDir" "oldDataDir" "Fail" + OLD_DATA_DIR="${YAML_VALUE}" +} + +# Get protocol value of connector +getXmlConnectorProtocol () { + local i="$1" + local filePath="$2" + local fileName="$3" + local protocolValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@protocol' ${filePath}/${fileName} 2>/dev/null |awk -F"=" '{print $2}' | tr -d '"') + echo -e "${protocolValue}" +} + +# Get all attributes of connector +getXmlConnectorAttributes () { + local i="$1" + local filePath="$2" + local fileName="$3" + local connectorAttributes=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@*' ${filePath}/${fileName} 2>/dev/null) + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + echo "${connectorAttributes}" +} + +# Get port value of connector +getXmlConnectorPort () { + local i="$1" + local filePath="$2" + local fileName="$3" + local portValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@port' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${portValue}" +} + +# Get maxThreads value of connector +getXmlConnectorMaxThreads () { + local i="$1" + local filePath="$2" + local fileName="$3" + local maxThreadValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@maxThreads' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${maxThreadValue}" +} +# Get sendReasonPhrase value of connector +getXmlConnectorSendReasonPhrase () { + local i="$1" + local filePath="$2" + local fileName="$3" + local sendReasonPhraseValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@sendReasonPhrase' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${sendReasonPhraseValue}" +} +# Get relaxedPathChars value of connector +getXmlConnectorRelaxedPathChars () { + local i="$1" + local filePath="$2" + local fileName="$3" + local relaxedPathCharsValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@relaxedPathChars' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + # strip leading and trailing spaces + relaxedPathCharsValue=$(io_trim "${relaxedPathCharsValue}") + echo -e "${relaxedPathCharsValue}" +} +# Get relaxedQueryChars value of connector +getXmlConnectorRelaxedQueryChars () { + local i="$1" + local filePath="$2" + local fileName="$3" + local relaxedQueryCharsValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@relaxedQueryChars' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + # strip leading and trailing spaces + relaxedQueryCharsValue=$(io_trim "${relaxedQueryCharsValue}") + echo -e "${relaxedQueryCharsValue}" +} + +# Updating system.yaml with Connector port +setConnectorPort () { + local yamlPath="$1" + local valuePort="$2" + local portYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${valuePort}" ]]; then + warn "port value is empty, could not migrate to system.yaml" + return + fi + ## Getting port yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" portYamlPath "Warning" + portYamlPath="${YAML_VALUE}" + if [[ -z "${portYamlPath}" ]]; then + return + fi + setSystemValue "${portYamlPath}" "${valuePort}" "${SYSTEM_YAML_PATH}" + logger "Setting [${portYamlPath}] with value [${valuePort}] in system.yaml" +} + +# Updating system.yaml with Connector maxThreads +setConnectorMaxThread () { + local yamlPath="$1" + local threadValue="$2" + local maxThreadYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${threadValue}" ]]; then + return + fi + ## Getting max Threads yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" maxThreadYamlPath "Warning" + maxThreadYamlPath="${YAML_VALUE}" + if [[ -z "${maxThreadYamlPath}" ]]; then + return + fi + setSystemValue "${maxThreadYamlPath}" "${threadValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${maxThreadYamlPath}] with value [${threadValue}] in system.yaml" +} + +# Updating system.yaml with Connector sendReasonPhrase +setConnectorSendReasonPhrase () { + local yamlPath="$1" + local sendReasonPhraseValue="$2" + local sendReasonPhraseYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${sendReasonPhraseValue}" ]]; then + return + fi + ## Getting sendReasonPhrase yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" sendReasonPhraseYamlPath "Warning" + sendReasonPhraseYamlPath="${YAML_VALUE}" + if [[ -z "${sendReasonPhraseYamlPath}" ]]; then + return + fi + setSystemValue "${sendReasonPhraseYamlPath}" "${sendReasonPhraseValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${sendReasonPhraseYamlPath}] with value [${sendReasonPhraseValue}] in system.yaml" +} + +# Updating system.yaml with Connector relaxedPathChars +setConnectorRelaxedPathChars () { + local yamlPath="$1" + local relaxedPathCharsValue="$2" + local relaxedPathCharsYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${relaxedPathCharsValue}" ]]; then + return + fi + ## Getting relaxedPathChars yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" relaxedPathCharsYamlPath "Warning" + relaxedPathCharsYamlPath="${YAML_VALUE}" + if [[ -z "${relaxedPathCharsYamlPath}" ]]; then + return + fi + setSystemValue "${relaxedPathCharsYamlPath}" "${relaxedPathCharsValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${relaxedPathCharsYamlPath}] with value [${relaxedPathCharsValue}] in system.yaml" +} + +# Updating system.yaml with Connector relaxedQueryChars +setConnectorRelaxedQueryChars () { + local yamlPath="$1" + local relaxedQueryCharsValue="$2" + local relaxedQueryCharsYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${relaxedQueryCharsValue}" ]]; then + return + fi + ## Getting relaxedQueryChars yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" relaxedQueryCharsYamlPath "Warning" + relaxedQueryCharsYamlPath="${YAML_VALUE}" + if [[ -z "${relaxedQueryCharsYamlPath}" ]]; then + return + fi + setSystemValue "${relaxedQueryCharsYamlPath}" "${relaxedQueryCharsValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${relaxedQueryCharsYamlPath}] with value [${relaxedQueryCharsValue}] in system.yaml" +} + +# Updating system.yaml with Connectors configurations +setConnectorExtraConfig () { + local yamlPath="$1" + local connectorAttributes="$2" + local extraConfigPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${connectorAttributes}" ]]; then + return + fi + ## Getting extraConfig yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" extraConfig "Warning" + extraConfigPath="${YAML_VALUE}" + if [[ -z "${extraConfigPath}" ]]; then + return + fi + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + setSystemValue "${extraConfigPath}" "${connectorAttributes}" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConfigPath}] with connector attributes in system.yaml" +} + +# Updating system.yaml with extra Connectors +setExtraConnector () { + local yamlPath="$1" + local extraConnector="$2" + local extraConnectorYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${extraConnector}" ]]; then + return + fi + ## Getting extraConnecotr yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" extraConnectorYamlPath "Warning" + extraConnectorYamlPath="${YAML_VALUE}" + if [[ -z "${extraConnectorYamlPath}" ]]; then + return + fi + getYamlValue "${extraConnectorYamlPath}" "${SYSTEM_YAML_PATH}" "false" + local connectorExtra="${YAML_VALUE}" + if [[ -z "${connectorExtra}" ]]; then + setSystemValue "${extraConnectorYamlPath}" "${extraConnector}" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConnectorYamlPath}] with extra connectors in system.yaml" + else + setSystemValue "${extraConnectorYamlPath}" "\"${connectorExtra} ${extraConnector}\"" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConnectorYamlPath}] with extra connectors in system.yaml" + fi +} + +# Migrate extra connectors to system.yaml +migrateExtraConnectors () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local excludeDefaultPort="$4" + local i="$5" + local extraConfig= + local extraConnector= + if [[ "${excludeDefaultPort}" == "yes" ]]; then + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + [[ "${portValue}" != "${DEFAULT_ACCESS_PORT}" && "${portValue}" != "${DEFAULT_RT_PORT}" ]] || continue + extraConnector=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']' ${filePath}/${fileName} 2>/dev/null) + setExtraConnector "${EXTRA_CONFIG_YAMLPATH}" "${extraConnector}" + done + else + extraConnector=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']' ${filePath}/${fileName} 2>/dev/null) + setExtraConnector "${EXTRA_CONFIG_YAMLPATH}" "${extraConnector}" + fi +} + +# Migrate connector configurations +migrateConnectorConfig () { + local i="$1" + local protocolType="$2" + local portValue="$3" + local connectorPortYamlPath="$4" + local connectorMaxThreadYamlPath="$5" + local connectorAttributesYamlPath="$6" + local filePath="$7" + local fileName="$8" + local connectorSendReasonPhraseYamlPath="$9" + local connectorRelaxedPathCharsYamlPath="${10}" + local connectorRelaxedQueryCharsYamlPath="${11}" + + # migrate port + setConnectorPort "${connectorPortYamlPath}" "${portValue}" + + # migrate maxThreads + local maxThreadValue=$(getXmlConnectorMaxThreads "$i" "${filePath}" "${fileName}") + setConnectorMaxThread "${connectorMaxThreadYamlPath}" "${maxThreadValue}" + + # migrate sendReasonPhrase + local sendReasonPhraseValue=$(getXmlConnectorSendReasonPhrase "$i" "${filePath}" "${fileName}") + setConnectorSendReasonPhrase "${connectorSendReasonPhraseYamlPath}" "${sendReasonPhraseValue}" + + # migrate relaxedPathChars + local relaxedPathCharsValue=$(getXmlConnectorRelaxedPathChars "$i" "${filePath}" "${fileName}") + setConnectorRelaxedPathChars "${connectorRelaxedPathCharsYamlPath}" "\"${relaxedPathCharsValue}\"" + # migrate relaxedQueryChars + local relaxedQueryCharsValue=$(getXmlConnectorRelaxedQueryChars "$i" "${filePath}" "${fileName}") + setConnectorRelaxedQueryChars "${connectorRelaxedQueryCharsYamlPath}" "\"${relaxedQueryCharsValue}\"" + + # migrate all attributes to extra config except port , maxThread , sendReasonPhrase ,relaxedPathChars and relaxedQueryChars + local connectorAttributes=$(getXmlConnectorAttributes "$i" "${filePath}" "${fileName}") + connectorAttributes=$(echo "${connectorAttributes}" | sed 's/port="'${portValue}'"//g' | sed 's/maxThreads="'${maxThreadValue}'"//g' | sed 's/sendReasonPhrase="'${sendReasonPhraseValue}'"//g' | sed 's/relaxedPathChars="\'${relaxedPathCharsValue}'\"//g' | sed 's/relaxedQueryChars="\'${relaxedQueryCharsValue}'\"//g') + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + setConnectorExtraConfig "${connectorAttributesYamlPath}" "${connectorAttributes}" +} + +# Check for default port 8040 and 8081 in connectors and migrate +migrateConnectorPort () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local defaultPort="$4" + local connectorPortYamlPath="$5" + local connectorMaxThreadYamlPath="$6" + local connectorAttributesYamlPath="$7" + local connectorSendReasonPhraseYamlPath="$8" + local connectorRelaxedPathCharsYamlPath="$9" + local connectorRelaxedQueryCharsYamlPath="${10}" + local portYamlPath= + local maxThreadYamlPath= + local status= + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" == *AJP* ]] && continue + [[ "${portValue}" != "${defaultPort}" ]] && continue + if [[ "${portValue}" == "${DEFAULT_RT_PORT}" ]]; then + RT_DEFAULTPORT_STATUS=success + else + AC_DEFAULTPORT_STATUS=success + fi + migrateConnectorConfig "${i}" "${protocolType}" "${portValue}" "${connectorPortYamlPath}" "${connectorMaxThreadYamlPath}" "${connectorAttributesYamlPath}" "${filePath}" "${fileName}" "${connectorSendReasonPhraseYamlPath}" "${connectorRelaxedPathCharsYamlPath}" "${connectorRelaxedQueryCharsYamlPath}" + done +} + +# migrate to extra, connector having default port and protocol is AJP +migrateDefaultPortIfAjp () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local defaultPort="$4" + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" != *AJP* ]] && continue + [[ "${portValue}" != "${defaultPort}" ]] && continue + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + done + +} + +# Comparing max threads in connectors +compareMaxThreads () { + local firstConnectorMaxThread="$1" + local firstConnectorNode="$2" + local secondConnectorMaxThread="$3" + local secondConnectorNode="$4" + local filePath="$5" + local fileName="$6" + + # choose higher maxThreads connector as Artifactory. + if [[ "${firstConnectorMaxThread}" -gt ${secondConnectorMaxThread} || "${firstConnectorMaxThread}" -eq ${secondConnectorMaxThread} ]]; then + # maxThread is higher in firstConnector, + # Taking firstConnector as Artifactory and SecondConnector as Access + # maxThread is equal in both connector,considering firstConnector as Artifactory and SecondConnector as Access + local rtPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + else + # maxThread is higher in SecondConnector, + # Taking SecondConnector as Artifactory and firstConnector as Access + local rtPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi +} + +# Check max threads exist to compare +maxThreadsExistToCompare () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local firstConnectorMaxThread= + local secondConnectorMaxThread= + local firstConnectorNode= + local secondConnectorNode= + local status=success + local firstnode=fail + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + if [[ ${protocolType} == *AJP* ]]; then + # Migrate Connectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + continue + fi + # store maxthreads value of each connector + if [[ ${firstnode} == "fail" ]]; then + firstConnectorMaxThread=$(getXmlConnectorMaxThreads "${i}" "${filePath}" "${fileName}") + firstConnectorNode="${i}" + firstnode=success + else + secondConnectorMaxThread=$(getXmlConnectorMaxThreads "${i}" "${filePath}" "${fileName}") + secondConnectorNode="${i}" + fi + done + [[ -z "${firstConnectorMaxThread}" ]] && status=fail + [[ -z "${secondConnectorMaxThread}" ]] && status=fail + # maxThreads is set, now compare MaxThreads + if [[ "${status}" == "success" ]]; then + compareMaxThreads "${firstConnectorMaxThread}" "${firstConnectorNode}" "${secondConnectorMaxThread}" "${secondConnectorNode}" "${filePath}" "${fileName}" + else + # Assume first connector is RT, maxThreads is not set in both connectors + local rtPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi +} + +migrateExtraBasedOnNonAjpCount () { + local nonAjpCount="$1" + local filePath="$2" + local fileName="$3" + local connectorCount="$4" + local i="$5" + + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + if [[ "${protocolType}" == *AJP* ]]; then + if [[ "${nonAjpCount}" -eq 1 ]]; then + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + continue + else + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + continue + fi + fi +} + +# find RT and AC Connector +findRtAndAcConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local initialAjpCount=0 + local nonAjpCount=0 + + # get the count of non AJP + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" != *AJP* ]] || continue + nonAjpCount=$((initialAjpCount+1)) + initialAjpCount="${nonAjpCount}" + done + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as access and artifactory connectors + # Mark port as 8040 for access + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + done + elif [[ "${nonAjpCount}" -eq 2 ]]; then + # compare maxThreads in both connectors + maxThreadsExistToCompare "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${nonAjpCount}" -gt 2 ]]; then + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # setting with default port in system.yaml + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +# get the count of non AJP +getCountOfNonAjp () { + local port="$1" + local connectorCount="$2" + local filePath=$3 + local fileName=$4 + local initialNonAjpCount=0 + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${portValue}" != "${port}" ]] || continue + [[ "${protocolType}" != *AJP* ]] || continue + local nonAjpCount=$((initialNonAjpCount+1)) + initialNonAjpCount="${nonAjpCount}" + done + echo -e "${nonAjpCount}" +} + +# Find for access connector +findAcConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + + # get the count of non AJP + local nonAjpCount=$(getCountOfNonAjp "${DEFAULT_RT_PORT}" "${connectorCount}" "${filePath}" "${fileName}") + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as access connector and mark port as that of connector + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" != "${DEFAULT_RT_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi + done + elif [[ "${nonAjpCount}" -gt 1 ]]; then + # Take RT properties into access with 8040 + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" == "${DEFAULT_RT_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + fi + done + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # Add RT connector details as access connector and mark port as 8040 + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +# Find for artifactory connector +findRtConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + + # get the count of non AJP + local nonAjpCount=$(getCountOfNonAjp "${DEFAULT_ACCESS_PORT}" "${connectorCount}" "${filePath}" "${fileName}") + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as RT connector + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" != "${DEFAULT_ACCESS_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + fi + done + elif [[ "${nonAjpCount}" -gt 1 ]]; then + # Take access properties into artifactory with 8081 + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" == "${DEFAULT_ACCESS_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + fi + done + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # Add access connector details as RT connector and mark as ${DEFAULT_RT_PORT} + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +checkForTlsConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + local sslProtocolValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@sslProtocol' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + if [[ "${sslProtocolValue}" == "TLS" ]]; then + bannerImportant "NOTE: Ignoring TLS connector during migration, modify the system yaml to enable TLS. Original server.xml is saved in path [${filePath}/${fileName}]" + TLS_CONNECTOR_EXISTS=${FLAG_Y} + continue + fi + done +} + +# set custom tomcat server Listeners to system.yaml +setListenerConnector () { + local filePath="$1" + local fileName="$2" + local listenerCount="$3" + for ((i = 1 ; i <= "${listenerCount}" ; i++)) + do + local listenerConnector=$($LIBXML2_PATH --xpath '//Server/Listener['$i']' ${filePath}/${fileName} 2>/dev/null) + local listenerClassName=$($LIBXML2_PATH --xpath '//Server/Listener['$i']/@className' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + if [[ "${listenerClassName}" == *Apr* ]]; then + setExtraConnector "${EXTRA_LISTENER_CONFIG_YAMLPATH}" "${listenerConnector}" + fi + done +} +# add custom tomcat server Listeners +addTomcatServerListeners () { + local filePath="$1" + local fileName="$2" + local listenerCount="$3" + if [[ "${listenerCount}" == "0" ]]; then + logger "No listener connectors found in the [${filePath}/${fileName}],skipping migration of listener connectors" + else + setListenerConnector "${filePath}" "${fileName}" "${listenerCount}" + setSystemValue "${RT_TOMCAT_HTTPSCONNECTOR_ENABLED}" "true" "${SYSTEM_YAML_PATH}" + logger "Setting [${RT_TOMCAT_HTTPSCONNECTOR_ENABLED}] with value [true] in system.yaml" + fi +} + +# server.xml migration operations +xmlMigrateOperation () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local listenerCount="$4" + RT_DEFAULTPORT_STATUS=fail + AC_DEFAULTPORT_STATUS=fail + TLS_CONNECTOR_EXISTS=${FLAG_N} + + # Check for connector with TLS , if found ignore migrating it + checkForTlsConnector "${filePath}" "${fileName}" "${connectorCount}" + if [[ "${TLS_CONNECTOR_EXISTS}" == "${FLAG_Y}" ]]; then + return + fi + addTomcatServerListeners "${filePath}" "${fileName}" "${listenerCount}" + # Migrate RT default port from connectors + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + # Migrate to extra if RT default ports are AJP + migrateDefaultPortIfAjp "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" + # Migrate AC default port from connectors + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${AC_SENDREASONPHRASE_YAMLPATH}" + # Migrate to extra if access default ports are AJP + migrateDefaultPortIfAjp "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" + + if [[ "${AC_DEFAULTPORT_STATUS}" == "success" && "${RT_DEFAULTPORT_STATUS}" == "success" ]]; then + # RT and AC default port found + logger "Artifactory 8081 and Access 8040 default port are found" + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "success" && "${RT_DEFAULTPORT_STATUS}" == "fail" ]]; then + # Only AC default port found,find RT connector + logger "Found Access default 8040 port" + findRtConnector "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "fail" && "${RT_DEFAULTPORT_STATUS}" == "success" ]]; then + # Only RT default port found,find AC connector + logger "Found Artifactory default 8081 port" + findAcConnector "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "fail" && "${RT_DEFAULTPORT_STATUS}" == "fail" ]]; then + # RT and AC default port not found, find connector + logger "Artifactory 8081 and Access 8040 default port are not found" + findRtAndAcConnector "${filePath}" "${fileName}" "${connectorCount}" + fi +} + +# get count of connectors +getXmlConnectorCount () { + local filePath="$1" + local fileName="$2" + local count=$($LIBXML2_PATH --xpath 'count(/Server/Service/Connector)' ${filePath}/${fileName}) + echo -e "${count}" +} + +# get count of listener connectors +getTomcatServerListenersCount () { + local filePath="$1" + local fileName="$2" + local count=$($LIBXML2_PATH --xpath 'count(/Server/Listener)' ${filePath}/${fileName}) + echo -e "${count}" +} + +# Migrate server.xml configuration to system.yaml +migrateXmlFile () { + local xmlFiles= + local fileName= + local filePath= + local sourceFilePath= + DEFAULT_ACCESS_PORT="8040" + DEFAULT_RT_PORT="8081" + AC_PORT_YAMLPATH="migration.xmlFiles.serverXml.access.port" + AC_MAXTHREADS_YAMLPATH="migration.xmlFiles.serverXml.access.maxThreads" + AC_SENDREASONPHRASE_YAMLPATH="migration.xmlFiles.serverXml.access.sendReasonPhrase" + AC_EXTRACONFIG_YAMLPATH="migration.xmlFiles.serverXml.access.extraConfig" + RT_PORT_YAMLPATH="migration.xmlFiles.serverXml.artifactory.port" + RT_MAXTHREADS_YAMLPATH="migration.xmlFiles.serverXml.artifactory.maxThreads" + RT_SENDREASONPHRASE_YAMLPATH='migration.xmlFiles.serverXml.artifactory.sendReasonPhrase' + RT_RELAXEDPATHCHARS_YAMLPATH='migration.xmlFiles.serverXml.artifactory.relaxedPathChars' + RT_RELAXEDQUERYCHARS_YAMLPATH='migration.xmlFiles.serverXml.artifactory.relaxedQueryChars' + RT_EXTRACONFIG_YAMLPATH="migration.xmlFiles.serverXml.artifactory.extraConfig" + ROUTER_PORT_YAMLPATH="migration.xmlFiles.serverXml.router.port" + EXTRA_CONFIG_YAMLPATH="migration.xmlFiles.serverXml.extra.config" + EXTRA_LISTENER_CONFIG_YAMLPATH="migration.xmlFiles.serverXml.extra.listener" + RT_TOMCAT_HTTPSCONNECTOR_ENABLED="artifactory.tomcat.httpsConnector.enabled" + + retrieveYamlValue "migration.xmlFiles" "xmlFiles" "Skip" + xmlFiles="${YAML_VALUE}" + if [[ -z "${xmlFiles}" ]]; then + return + fi + bannerSection "PROCESSING MIGRATION OF XML FILES" + retrieveYamlValue "migration.xmlFiles.serverXml.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + if [[ -z "${fileName}" ]]; then + return + fi + bannerSubSection "Processing Migration of $fileName" + retrieveYamlValue "migration.xmlFiles.serverXml.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + if [[ -z "${filePath}" ]]; then + return + fi + # prepend NEW_DATA_DIR only if filePath is relative path + sourceFilePath=$(prependDir "${filePath}" "${NEW_DATA_DIR}/${filePath}") + if [[ "$(checkFileExists "${sourceFilePath}/${fileName}")" == "true" ]]; then + logger "File [${fileName}] is found in path [${sourceFilePath}]" + local connectorCount=$(getXmlConnectorCount "${sourceFilePath}" "${fileName}") + if [[ "${connectorCount}" == "0" ]]; then + logger "No connectors found in the [${filePath}/${fileName}],skipping migration of xml configuration" + return + fi + local listenerCount=$(getTomcatServerListenersCount "${sourceFilePath}" "${fileName}") + xmlMigrateOperation "${sourceFilePath}" "${fileName}" "${connectorCount}" "${listenerCount}" + else + logger "File [${fileName}] is not found in path [${sourceFilePath}] to migrate" + fi +} + +compareArtifactoryUser () { + local property="$1" + local oldPropertyValue="$2" + local newPropertyValue="$3" + local yamlPath="$4" + local sourceFile="$5" + + if [[ "${oldPropertyValue}" != "${newPropertyValue}" ]]; then + setSystemValue "${yamlPath}" "${oldPropertyValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" + else + logger "No change in property [${property}] value in [${sourceFile}] to migrate" + fi +} + +migrateReplicator () { + local property="$1" + local oldPropertyValue="$2" + local yamlPath="$3" + + setSystemValue "${yamlPath}" "${oldPropertyValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" +} + +compareJavaOptions () { + local property="$1" + local oldPropertyValue="$2" + local newPropertyValue="$3" + local yamlPath="$4" + local sourceFile="$5" + local oldJavaOption= + local newJavaOption= + local extraJavaOption= + local check=false + local success=true + local status=true + + oldJavaOption=$(echo "${oldPropertyValue}" | awk 'BEGIN{FS=OFS="\""}{for(i=2;i.+)\.{{ include "artifactory-ha.fullname" . }} {{ include "artifactory-ha.fullname" . }} +{{ tpl (include "artifactory.nginx.hosts" .) . }}; + +if ($http_x_forwarded_proto = '') { + set $http_x_forwarded_proto $scheme; +} +set $host_port {{ .Values.nginx.https.externalPort }}; +if ( $scheme = "http" ) { + set $host_port {{ .Values.nginx.http.externalPort }}; +} +## Application specific logs +## access_log /var/log/nginx/artifactory-access.log timing; +## error_log /var/log/nginx/artifactory-error.log; +rewrite ^/artifactory/?$ / redirect; +if ( $repo != "" ) { + rewrite ^/(v1|v2)/(.*) /artifactory/api/docker/$repo/$1/$2 break; +} +chunked_transfer_encoding on; +client_max_body_size 0; + +location / { + proxy_read_timeout 900; + proxy_pass_header Server; + proxy_cookie_path ~*^/.* /; + proxy_pass {{ include "artifactory-ha.scheme" . }}://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalPort }}/; + {{- if .Values.nginx.service.ssloffload}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$host_port; + proxy_set_header X-Forwarded-Port $server_port; + {{- end }} + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- if .Values.nginx.disableProxyBuffering}} + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + {{- end }} + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + location /artifactory/ { + if ( $request_uri ~ ^/artifactory/(.*)$ ) { + proxy_pass http://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/$1; + } + proxy_pass http://{{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/; + } + location /pipelines/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + {{- if .Values.router.tlsEnabled }} + proxy_pass https://{{ include "artifactory-ha.fullname" . }}:{{ .Values.router.internalPort }}; + {{- else }} + proxy_pass http://{{ include "artifactory-ha.fullname" . }}:{{ .Values.router.internalPort }}; + {{- end }} + } +} +} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/files/nginx-main-conf.yaml b/charts/jfrog/artifactory-ha/107.90.9/files/nginx-main-conf.yaml new file mode 100644 index 0000000000..78cecea6a1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/files/nginx-main-conf.yaml @@ -0,0 +1,83 @@ +# Main Nginx configuration file +worker_processes 4; + +{{- if .Values.nginx.logs.stderr }} +error_log stderr {{ .Values.nginx.logs.level }}; +{{- else -}} +error_log {{ .Values.nginx.persistence.mountPath }}/logs/error.log {{ .Values.nginx.logs.level }}; +{{- end }} +pid /var/run/nginx.pid; + +{{- if .Values.artifactory.ssh.enabled }} +## SSH Server Configuration +stream { + server { + {{- if .Values.nginx.singleStackIPv6Cluster }} + listen [::]:{{ .Values.nginx.ssh.internalPort }}; + {{- else -}} + listen {{ .Values.nginx.ssh.internalPort }}; + {{- end }} + proxy_pass {{ include "artifactory-ha.fullname" . }}:{{ .Values.artifactory.ssh.externalPort }}; + } +} +{{- end }} + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + variables_hash_max_size 1024; + variables_hash_bucket_size 64; + server_names_hash_max_size 4096; + server_names_hash_bucket_size 128; + types_hash_max_size 2048; + types_hash_bucket_size 64; + proxy_read_timeout 2400s; + client_header_timeout 2400s; + client_body_timeout 2400s; + proxy_connect_timeout 75s; + proxy_send_timeout 2400s; + proxy_buffer_size 128k; + proxy_buffers 40 128k; + proxy_busy_buffers_size 128k; + proxy_temp_file_write_size 250m; + proxy_http_version 1.1; + client_body_buffer_size 128k; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + log_format timing 'ip = $remote_addr ' + 'user = \"$remote_user\" ' + 'local_time = \"$time_local\" ' + 'host = $host ' + 'request = \"$request\" ' + 'status = $status ' + 'bytes = $body_bytes_sent ' + 'upstream = \"$upstream_addr\" ' + 'upstream_time = $upstream_response_time ' + 'request_time = $request_time ' + 'referer = \"$http_referer\" ' + 'UA = \"$http_user_agent\"'; + + {{- if .Values.nginx.logs.stdout }} + access_log /dev/stdout timing; + {{- else -}} + access_log {{ .Values.nginx.persistence.mountPath }}/logs/access.log timing; + {{- end }} + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; + +} diff --git a/charts/jfrog/artifactory-ha/107.90.9/files/system.yaml b/charts/jfrog/artifactory-ha/107.90.9/files/system.yaml new file mode 100644 index 0000000000..3a1d93269d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/files/system.yaml @@ -0,0 +1,163 @@ +router: + serviceRegistry: + insecure: {{ .Values.router.serviceRegistry.insecure }} +shared: +{{- if .Values.artifactory.coldStorage.enabled }} + jfrogColdStorage: + coldInstanceEnabled: true +{{- end }} +{{ tpl (include "artifactory.metrics" .) . }} + logging: + consoleLog: + enabled: {{ .Values.artifactory.consoleLog }} + extraJavaOpts: > + -Dartifactory.graceful.shutdown.max.request.duration.millis={{ mul .Values.artifactory.terminationGracePeriodSeconds 1000 }} + -Dartifactory.access.client.max.connections={{ .Values.access.tomcat.connector.maxThreads }} + {{- with .Values.artifactory.primary.javaOpts }} + {{- if .corePoolSize }} + -Dartifactory.async.corePoolSize={{ .corePoolSize }} + {{- end }} + {{- if .xms }} + -Xms{{ .xms }} + {{- end }} + {{- if .xmx }} + -Xmx{{ .xmx }} + {{- end }} + {{- if .jmx.enabled }} + -Dcom.sun.management.jmxremote + -Dcom.sun.management.jmxremote.port={{ .jmx.port }} + -Dcom.sun.management.jmxremote.rmi.port={{ .jmx.port }} + -Dcom.sun.management.jmxremote.ssl={{ .jmx.ssl }} + {{- if .jmx.host }} + -Djava.rmi.server.hostname={{ tpl .jmx.host $ }} + {{- else }} + -Djava.rmi.server.hostname={{ template "artifactory-ha.fullname" $ }} + {{- end }} + {{- if .jmx.authenticate }} + -Dcom.sun.management.jmxremote.authenticate=true + -Dcom.sun.management.jmxremote.access.file={{ .jmx.accessFile }} + -Dcom.sun.management.jmxremote.password.file={{ .jmx.passwordFile }} + {{- else }} + -Dcom.sun.management.jmxremote.authenticate=false + {{- end }} + {{- end }} + {{- if .other }} + {{ .other }} + {{- end }} + {{- end }} + database: + allowNonPostgresql: {{ .Values.database.allowNonPostgresql }} + {{- if .Values.postgresql.enabled }} + type: postgresql + url: "jdbc:postgresql://{{ .Release.Name }}-postgresql:{{ .Values.postgresql.service.port }}/{{ .Values.postgresql.postgresqlDatabase }}" + host: "" + driver: org.postgresql.Driver + username: "{{ .Values.postgresql.postgresqlUsername }}" + {{ else }} + type: "{{ .Values.database.type }}" + driver: "{{ .Values.database.driver }}" + {{- end }} +artifactory: +{{- if or .Values.artifactory.haDataDir.enabled .Values.artifactory.haBackupDir.enabled }} + node: + {{- if .Values.artifactory.haDataDir.path }} + haDataDir: {{ .Values.artifactory.haDataDir.path }} + {{- end }} + {{- if .Values.artifactory.haBackupDir.path }} + haBackupDir: {{ .Values.artifactory.haBackupDir.path }} + {{- end }} +{{- end }} + database: + maxOpenConnections: {{ .Values.artifactory.database.maxOpenConnections }} + tomcat: + maintenanceConnector: + port: {{ .Values.artifactory.tomcat.maintenanceConnector.port }} + connector: + maxThreads: {{ .Values.artifactory.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.artifactory.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.artifactory.tomcat.connector.extraConfig }} +frontend: + session: + timeMinutes: {{ .Values.frontend.session.timeoutMinutes | quote }} +access: + runOnArtifactoryTomcat: {{ .Values.access.runOnArtifactoryTomcat | default false }} + database: + maxOpenConnections: {{ .Values.access.database.maxOpenConnections }} + {{- if not (.Values.access.runOnArtifactoryTomcat | default false) }} + extraJavaOpts: > + {{- if .Values.splitServicesToContainers }} + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=70 + {{- end }} + {{- with .Values.access.javaOpts }} + {{- if .other }} + {{ .other }} + {{- end }} + {{- end }} + {{- end }} + tomcat: + connector: + maxThreads: {{ .Values.access.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.access.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.access.tomcat.connector.extraConfig }} + {{- if .Values.access.database.enabled }} + type: "{{ .Values.access.database.type }}" + url: "{{ .Values.access.database.url }}" + driver: "{{ .Values.access.database.driver }}" + username: "{{ .Values.access.database.user }}" + password: "{{ .Values.access.database.password }}" + {{- end }} +{{- if .Values.mc.enabled }} +mc: + enabled: true + database: + maxOpenConnections: {{ .Values.mc.database.maxOpenConnections }} + idgenerator: + maxOpenConnections: {{ .Values.mc.idgenerator.maxOpenConnections }} + tomcat: + connector: + maxThreads: {{ .Values.mc.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.mc.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.mc.tomcat.connector.extraConfig }} +{{- end }} +metadata: + database: + maxOpenConnections: {{ .Values.metadata.database.maxOpenConnections }} +{{- if and .Values.jfconnect.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository)) }} +jfconnect: + enabled: true +{{- else }} +jfconnect: + enabled: false +jfconnect_service: + enabled: false +{{- end }} + +{{- if and .Values.federation.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository)) }} +federation: + enabled: true + embedded: {{ .Values.federation.embedded }} + extraJavaOpts: {{ .Values.federation.extraJavaOpts }} + port: {{ .Values.federation.internalPort }} +rtfs: + database: + driver: org.postgresql.Driver + type: postgresql + username: {{ .Values.federation.database.username }} + password: {{ .Values.federation.database.password }} + url: "jdbc:postgresql://{{ .Values.federation.database.host }}:{{ .Values.federation.database.port }}/{{ .Values.federation.database.name }}" +{{- else }} +federation: + enabled: false +{{- end }} +{{- if .Values.event.webhooks }} +event: + webhooks: {{ toYaml .Values.event.webhooks | nindent 6 }} +{{- end }} +{{- if .Values.evidence.enabled }} +evidence: + enabled: true +{{- else }} +evidence: + enabled: false +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/logo/artifactory-logo.png b/charts/jfrog/artifactory-ha/107.90.9/logo/artifactory-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6c23c5a7f87edaf49f883ffa99d875073e2285 GIT binary patch literal 82419 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 z zKRlv8)uyKuJ<|L{cE7kNX^5tf?>5g<$u3VtXX%#6Ye4p>-=WvfeM4aNl8nP`a-yYvuwiFyE@Q*b+AkLI}l zFnuDc1TWeOokNy+W>0Nff^_j}Ugr~Ht6qk4&CHu`*WLMr13mUoJw!P``+4TGKfXb~d z_WuGW>X-k?4XtH+n`HKURH4(;tPzuc*1~aGBM&={+U0IJ1rFUMnh@0VBF_LX|G4_K z$P-zk{Z=DbFeVNe<1GqjHzBqvprTH2!~vyNv@dU{eu2~Qp^eLeOsHXg`scYYV9Oq9 zAO~CW-Wju>2F6lO?$T#7j-?T3v*kV5bT|o)@>l&(>?ydyoNOZwio(xEi38O&(Vn{q zBly9#PZ9ocnWdpV$#rgOP{j7fWlhxIiEBEdx**HF^EKJqj{H8Wy4D4tmgi=(^HFqR z=0)1lMNuICK0VrVtX41CU5rYWS({8sJT4AdH{NIoxx?e_bJ{I0)miS1d*1Rmr$ye@ zDadHi8F`Hu(e|g&g?Np%;3Y~k1>dRAd6g7G;*B`eHUQih8(G``f^Cx8bD!3;R8_p+@5 zp+_7X47B`a>IUFGkvi!Pq)I7D_DVHIm2RcpTiB(Fcl)0G`emk*2+cN%C?=2}RzFty02em=3;T_hN7vea>VVR_4Ce`&PDe~tF(Si#08r+-3bmC%kZ1&*tc~3;=5UupXQhBLm z!o=~f$%A!yHIS+9zn$NOmJ33$CgN+U)MGHISHetIitgoJeq}C~jgiFwWnfE^6V%TG zwC#O~3ku}_V^L11MaLyi*9vH80i$V&>m*Wn@W?Id287{&AJS_9$Z69lB+ z0iwD)V{#I4aNsRoaHXM3%InD!7$581@CQ^>n?e>Iv_@HuF5OEDbvBSd)GeIk0#-Uw zJE!UN_Ro||I-V+@{eE$Z^3n%j3l6+yLVX}6b`w9^qil=+z~htqsITCME1|i*|7?3& z^(*-;iuUoxK{sxVrO=GJ)PagdryU@Zz~3t`c^JI`>RgHXMe~p#sAwhYP)Hw4neYne zu8(e}w?*lJv2Biiu`99rZaCWHI-)DTpza=!#ReUlitU3tfW|l5mAI##GzOp?L1yej!AGgoh~7 zLcM}31WnvN+b1zc5U2eFAjqPmohrzLi{spqEb0JwTqN%7T4O*NQ}{k+u1db+SA{9O zsuQa4i#V4uROcX0=fydg15R_{G|jdswQbb8hAB-{NMi`GJY*T4`bPop7hY}xV|%nW zt{82zZvn8Gt;3|FePWInP#Y^rux^!{FPq+^ecU!<%SKoy+ATmlh=(S|P_qbyNItMW5Gxm+no+A4 zh`{o15#PE(+1+h8C%Yr_m6Dbe?c&ciCgjb)IO`l=P>q*mX6iA3r;LzW*vk7I~u33(y0+HE1j z)S2LkkZ|HqbbggMOt`RFUy?Es>;hz^>IEgODepc5?P9X~z%`Sxr*eg2M2S_8KS8a5 z*!07)88oDu1@FMKj(8tEOIh!CSjb@)YWoy&+*M!3R{S9y>>Mrtz* zA;ea@reKL+R;)VYXGwi`jsGk_P9UoojQRyg5rHAz{=pqcMro+x+g(<3ysocCzI9I*@6^aK}D%aUG(t@-Hzv>QrS2? zjm^&tjv&>-49k8D?s7va7%Gc;u16iPb35N87)rSUgOq@4kt9C?$33Etmh4Cw9o`C- zE@P07;`K^L5&*pht^;se7HfkJC{YgP6t?h}%NOepBLgF{ZQ`G*(4-)n0f)%;tS|{_ z4yby8G=ZV+f*qiiHFD&~lq9s9C=>U0R_>py-1U>MkK9B>Fi26s5W5r2FH~xqwmHrnioHa+DFeh} zp&rH+DjgvCBU6m#4#gWVJQk6+cJO`8t8jY_J7biGJ0>Rj3>v!;%?h~xkh|S8_36|E zdS+leh+bCe6G)3=?+G(SzH|g=f!=kahz%@s*SQYxAonz9e|%daI_GBoZr!>={wI$f zI_FFgZ(gKMPG3O=Hu@$8+|KO&U-UOzM1@f^nk_0FK$JzP+kzucfU^bPxeD@dEH=Xp z{-KE{0W>(y8H>!^9oeuasA-QaKM|W%VQi&q^iwgT)fGj7U`@d5(Sq@3Vc^~9z$ zszEi70@kt-StV=D3)njQL{1`^nsbDe_M9CPqX^hV{DEIzK6tKXAqJZo8pk}!-5*7n&$8ZtIjuB z#PUjhMv+HUORE5Bra|7vArybX9OYS!rM#}RX#A2lWkZ_y2h&#BJo6uKPH!i^+i?`g zFfbo?2nq&S!=xy`5e9C1{YP>U=a*&q2Q~T^V3v4pQkc?+(zP488})=YopPp3oDMcf z<>pmZ%0@eqh{4j};{yNaKxB>fL-eD3Owlew8P~;Ppw2ie$wC`ac>Q0DP59?LyUxcO z3xHU3&HmKxf+xNGPynePgCsMLya4PA1jRzW9IB;6IOA{>v9O}i8Zn$$Y;*)BnO0-+ zg_rPGNN^yhrz^*`o<}dVnb8Ab`XH@)V1!AUk`%Mnb^6kcUs>Rg!$G)Wpnm%^LMnzR z;E!t5iV>7d$a_@!t+||kDJ8ra#?^lar7t%O!#2AwG5Z*1%KZrm_csVjw9nMT+WY_ryW$JU_pM1Ht_NheERD z1)D2lsagaW_``3$@rcIH6ovxDrAg+ut@@dB)$Gt52mK7HEk)tpUJA9v7XK0aH-Ij7 zSe@i<-j?Kqj_78RpLx|n&8>Dsjo!pleVA%mm_g`|>2W=$YRSVtI~@+STYQ0!v3!c0u|CMt$zmivKl8%S}U-mWuu6>vHhNuLjXT zIDm4{G$?02p=5rC0*zuP&_?tQ_#!L<+sTS}Kw(kKv$e*RqB;sY!Ls|+0m`t25$hhv*tjT<<#*vMuBh>X1QR5MJ41h?!EnJlo*^FG43_$1GbQXr7jP$#k369#Z zn;)fAok*_%<_8yz7JxZ#xrp|I&YB68fw?HdY|4TvsUgrDpS!blGt*^?j2=d$i1#hj z$OA|JLE+<4w+0U<0D}?{7n_mdQpXbk5?%r84Ei~4O!+yPDr~30v%7cEnW`ZRx{Zzoi@ z|Kz1lR7v?S9y?i?0xT0mg+>Gdo;VNVe4i+-A>%fRQ~jS*3uw(_*x-@SRS4vfk0=&# zcoBJG3wR>9O=4~nU%}%PnIK)!q`c?vdL(2q>X5+)V8ll^W|sV+Sr>-Fl-CP)c;kX3 z>wQWJO_t|PsXiPbdK1W=0%3xM@}R&tzyOD##$p->8rMWX93eH~cxt;&?t(QcyqQTZ zyNV_T?-&J1aOv%)dJTA0goZ8ydj>H7_Mk~(0wguAQc}afVr+47IuabE`h}`UfxQ9# znVGCL_0TlZ?P*Q!0MCixy;AyB{f*$#jGc#D7{X@(R{)*#sMRd+A?20O1`eX9Cx^dF zQb({0DPhR_xq~g`?is87;uXbo!k`06wj%Y%vcYe#w|6w1cxcQ-U8Cq?n5n?VR2k|_ zNeGez;YEP(dcOQ8Zi9zzVR53!1CPA0&#iLiBzI2WrM7gNkoZ!09&P$7Dsgj^}+{dygKOk zDM^X;3oAc>Yf(0R=u-CDZxJU+{QMDWS54?V3t>lq!E6AK9kPI;NauLq)p0xrhJ3OF zDIBQB9=2%Ik5p*Wq8y=hq!Vz4cETmn)PcxAdP9qHPI#pnw6$XMANmD9zz0DO137o2 z?|k$Q>aztkwr&}XP&N(k|9S`3OSFt1I@qEma3KNsoCgq2_AW-1H{W=fF^O~#_+`76 z)voHymo3~DQO##;h6F46cAyj(_nh;mpza(A?@x?R)8H6#<`OIiS8b!H)?}T{zs9K!zG$1xZjU$lJ-GLX4%v8yY&r>=GRDnpe zvah#Ne6YQ}EhV!{?hTIOEw0C00|9+~S{d1PFoP`g02V+M$!c7c0SX7lK4+<V2W^2m|K1Mj{v^ zfc_UidnUY9NM-&rvz8vMc`hup zjDF!+rWH^rOY8U@XV_Ps6m)qHMuCGiB&SuOdq6ux{-FC{1|+%4Q{)p#(yPrq$;>4M>B{14%!@LOQ>9FG~VHSa)zuJE2ELZJfIqAk1Xng6@drGzx1H zekgq$7+qT#JdvLZPbw2bICsmAvu(Qby<@}qgPa~({)&E7oDu)gPE;O(^r<99ZTJECjV<|NCNPSLP;U?Avpbpzz^TY(qh%ah zo-S660=@RoqTL|PkqpSn^(#rpiJ=VRFE%F{RU@63^+)dnTwTKAB9zDTST!N0>2wRXQ6vR=ElmR?(>P^~O51(Q@(=%!C0RUz{^X-|G;ldOn?%U=0oMs5D@AVRmdvoGFfR=oCDCZSkRTo;@v#hI|H zchw<9u{)&~h|1r=?@tfOXX1W04g&0TJviYZsjKJf9vIdWJx-7zqk6{< z%x_0;ebTb~=T&d^W+>7*+zEM#>r>%g@|F7Hlo$ene^8ajG_kyE0$fUGXO}u^-j)XW zwCb#dC5=84_Z2}sGmZsEa+P7QJlldExeCBiTFV4zzyiv&nnUhW6f~Vs9N{ctTutdn zkFs8!e(ky7Ry;O!yULi>vRDk$40Xj`O|`90^qBdnAT#U=0{r4#?oyo6-^Yit1kelA z5_*9O?_N8hR6Rk_I}Cw~Z5}Qc(W> zd@LA-P^WlmEyMHIAjVF@`UF|n|KC9J=~$DD^cf>cb>x2*h@^r0N{rHrRi!Xi{xkL* z0en$zDr3!GZ&-u#-Odoa7rQR%Q=3-){rSHwLYx0@i_jVPe{hN#!h1rLJXZSCd6xex zmn1n-`|6VYWRz99hcX;eGmh+_rC&;(tIhcyV{5d8-|{$uU0H7T99j7_++$4(N?^#y z6}N3_#-o|y?i0{GsjJ!q8Td2w0wbRgXk>Jf4z$m6?=1cT%2kvndTtvb2~-5&i<~$AuP206F)G|-s2?rBO0-NH|C;^J z7YN=pJhtn%i5imDuRJ2l`}}?^MU@cu;*02@ihv;`p*d_ zieDR-NycQ!7i|i4^p67h`_by&sE+%O0wLX4zwNJ4Rq}8e=Kih42!B6XdaZci_s&() z{5>i;&+Bf}|5eg|KlgDXo86`kz_)W8(kW z;s2jI9xDso0a2a{!^)oj@h}M;>K(I6p3uFJq($(ySdRG3-u&a~;rH?4i_|_e3jD`y zrTBXn*KdoTc3fj`|K$P}c->@>O1u(6C9ePdzN_G?*0uNl{U_>+SRsTiex{FZJNWO1 z(QJX_i=KaOO8y^5@)S7stQHQG@}~b8ER5pcME>o_-(vJnwEmruzqQIgt@U3Gi&Xi) zGxG0@{5vC*75L|(tQ8*or6vCPTK~M?zr=y*|C=+?;rW*Qw)l}Y9cCT;ck-C}(fGrc GZv9`RA2Y%L literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-ha/107.90.9/questions.yml b/charts/jfrog/artifactory-ha/107.90.9/questions.yml new file mode 100644 index 0000000000..14e9024e6d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/questions.yml @@ -0,0 +1,424 @@ +questions: +# Advance Settings +- variable: artifactory.masterKey + default: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + description: "Artifactory master key. For security reasons, we strongly recommend you generate your own master key using this command: 'openssl rand -hex 32'" + type: string + label: Artifactory master key + group: "Security Settings" + +# Container Images +- variable: defaultImage + default: true + description: "Use default Docker image" + label: Use Default Image + type: boolean + show_subquestion_if: false + group: "Container Images" + subquestions: + - variable: initContainerImage + default: "docker.bintray.io/alpine:3.12" + description: "Init image name" + type: string + label: Init image name + - variable: artifactory.image.repository + default: "docker.bintray.io/jfrog/artifactory-pro" + description: "Artifactory image name" + type: string + label: Artifactory Image Name + - variable: artifactory.image.version + default: "7.6.3" + description: "Artifactory image tag" + type: string + label: Artifactory Image Tag + - variable: nginx.image.repository + default: "docker.bintray.io/jfrog/nginx-artifactory-pro" + description: "Nginx image name" + type: string + label: Nginx Image Name + - variable: nginx.image.version + default: "7.6.3" + description: "Nginx image tag" + type: string + label: Nginx Image Tag + - variable: imagePullSecrets + description: "Image Pull Secret" + type: string + label: Image Pull Secret + +# Services and LoadBalancing Settings +- variable: artifactory.node.replicaCount + default: "2" + description: "Number of Secondary Nodes" + type: string + label: Number of Secondary Nodes + show_subquestion_if: true + group: "Services and Load Balancing" +- variable: ingress.enabled + default: false + description: "Expose app using Layer 7 Load Balancer - ingress" + type: boolean + label: Expose app using Layer 7 Load Balancer + show_subquestion_if: true + group: "Services and Load Balancing" + required: true + subquestions: + - variable: ingress.hosts[0] + default: "xip.io" + description: "Hostname to your artifactory installation" + type: hostname + required: true + label: Hostname + +# Nginx Settings +- variable: nginx.enabled + default: true + description: "Enable nginx server" + type: boolean + label: Enable Nginx Server + group: "Services and Load Balancing" + required: true + show_if: "ingress.enabled=false" +- variable: nginx.service.type + default: "LoadBalancer" + description: "Nginx service type" + type: enum + required: true + label: Nginx Service Type + show_if: "nginx.enabled=true&&ingress.enabled=false" + group: "Services and Load Balancing" + options: + - "ClusterIP" + - "NodePort" + - "LoadBalancer" +- variable: nginx.service.loadBalancerIP + default: "" + description: "Provide Static IP to configure with Nginx" + type: string + label: Config Nginx LoadBalancer IP + show_if: "nginx.enabled=true&&nginx.service.type=LoadBalancer&&ingress.enabled=false" + group: "Services and Load Balancing" +- variable: nginx.tlsSecretName + default: "" + description: "Provide SSL Secret name to configure with Nginx" + type: string + label: Config Nginx SSL Secret + show_if: "nginx.enabled=true&&ingress.enabled=false" + group: "Services and Load Balancing" +- variable: nginx.customArtifactoryConfigMap + default: "" + description: "Provide configMap name to configure Nginx with custom `artifactory.conf`" + type: string + label: ConfigMap for Nginx Artifactory Config + show_if: "nginx.enabled=true&&ingress.enabled=false" + group: "Services and Load Balancing" + +# Artifactory Storage Settings +- variable: artifactory.persistence.size + default: "50Gi" + description: "Artifactory persistent volume size" + type: string + label: Artifactory Persistent Volume Size + required: true + group: "Artifactory Storage" +- variable: artifactory.persistence.type + default: "file-system" + description: "Artifactory persistent volume size" + type: enum + label: Artifactory Persistent Storage Type + required: true + options: + - "file-system" + - "nfs" + - "google-storage" + - "aws-s3" + group: "Artifactory Storage" + +#Storage Type Settings +- variable: artifactory.persistence.nfs.ip + default: "" + type: string + group: "Artifactory Storage" + label: NFS Server IP + description: "NFS server IP" + show_if: "artifactory.persistence.type=nfs" +- variable: artifactory.persistence.nfs.haDataMount + default: "/data" + type: string + label: NFS Data Directory + description: "NFS data directory" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=nfs" +- variable: artifactory.persistence.nfs.haBackupMount + default: "/backup" + type: string + label: NFS Backup Directory + description: "NFS backup directory" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=nfs" +- variable: artifactory.persistence.nfs.dataDir + default: "/var/opt/jfrog/artifactory-ha" + type: string + label: HA Data Directory + description: "HA data directory" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=nfs" +- variable: artifactory.persistence.nfs.backupDir + default: "/var/opt/jfrog/artifactory-backup" + type: string + label: HA Backup Directory + description: "HA backup directory " + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=nfs" +- variable: artifactory.persistence.nfs.capacity + default: "200Gi" + type: string + label: NFS PVC Size + description: "NFS PVC size " + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=nfs" + +#Google storage settings +- variable: artifactory.persistence.googleStorage.bucketName + default: "artifactory-ha-gcp" + type: string + label: Google Storage Bucket Name + description: "Google storage bucket name" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=google-storage" +- variable: artifactory.persistence.googleStorage.identity + default: "" + type: string + label: Google Storage Service Account ID + description: "Google Storage service account id" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=google-storage" +- variable: artifactory.persistence.googleStorage.credential + default: "" + type: string + label: Google Storage Service Account Key + description: "Google Storage service account key" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=google-storage" +- variable: artifactory.persistence.googleStorage.path + default: "artifactory-ha/filestore" + type: string + label: Google Storage Path In Bucket + description: "Google Storage path in bucket" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=google-storage" +# awsS3 storage settings +- variable: artifactory.persistence.awsS3.bucketName + default: "artifactory-ha-aws" + type: string + label: AWS S3 Bucket Name + description: "AWS S3 bucket name" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=aws-s3" +- variable: artifactory.persistence.awsS3.region + default: "" + type: string + label: AWS S3 Bucket Region + description: "AWS S3 bucket region" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=aws-s3" +- variable: artifactory.persistence.awsS3.identity + default: "" + type: string + label: AWS S3 AWS_ACCESS_KEY_ID + description: "AWS S3 AWS_ACCESS_KEY_ID" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=aws-s3" +- variable: artifactory.persistence.awsS3.credential + default: "" + type: string + label: AWS S3 AWS_SECRET_ACCESS_KEY + description: "AWS S3 AWS_SECRET_ACCESS_KEY" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=aws-s3" +- variable: artifactory.persistence.awsS3.path + default: "artifactory-ha/filestore" + type: string + label: AWS S3 Path In Bucket + description: "AWS S3 path in bucket" + group: "Artifactory Storage" + show_if: "artifactory.persistence.type=aws-s3" + +# Database Settings +- variable: postgresql.enabled + default: true + description: "Enable PostgreSQL" + type: boolean + required: true + label: Enable PostgreSQL + group: "Database Settings" + show_subquestion_if: true + subquestions: + - variable: postgresql.postgresqlPassword + default: "" + description: "PostgreSQL password" + type: password + required: true + label: PostgreSQL Password + group: "Database Settings" + show_if: "postgresql.enabled=true" + - variable: postgresql.persistence.size + default: 20Gi + description: "PostgreSQL persistent volume size" + type: string + label: PostgreSQL Persistent Volume Size + show_if: "postgresql.enabled=true" + - variable: postgresql.persistence.storageClass + default: "" + description: "If undefined or null, uses the default StorageClass. Default to null" + type: storageclass + label: Default StorageClass for PostgreSQL + show_if: "postgresql.enabled=true" + - variable: postgresql.resources.requests.cpu + default: "200m" + description: "PostgreSQL initial cpu request" + type: string + label: PostgreSQL Initial CPU Request + show_if: "postgresql.enabled=true" + - variable: postgresql.resources.requests.memory + default: "500Mi" + description: "PostgreSQL initial memory request" + type: string + label: PostgreSQL Initial Memory Request + show_if: "postgresql.enabled=true" + - variable: postgresql.resources.limits.cpu + default: "1" + description: "PostgreSQL cpu limit" + type: string + label: PostgreSQL CPU Limit + show_if: "postgresql.enabled=true" + - variable: postgresql.resources.limits.memory + default: "1Gi" + description: "PostgreSQL memory limit" + type: string + label: PostgreSQL Memory Limit + show_if: "postgresql.enabled=true" +- variable: database.type + default: "postgresql" + description: "xternal database type (postgresql, mysql, oracle or mssql)" + type: enum + required: true + label: External Database Type + group: "Database Settings" + show_if: "postgresql.enabled=false" + options: + - "postgresql" + - "mysql" + - "oracle" + - "mssql" +- variable: database.url + default: "" + description: "External database URL. If you set the url, leave host and port empty" + type: string + label: External Database URL + group: "Database Settings" + show_if: "postgresql.enabled=false" +- variable: database.host + default: "" + description: "External database hostname" + type: string + label: External Database Hostname + group: "Database Settings" + show_if: "postgresql.enabled=false" +- variable: database.port + default: "" + description: "External database port" + type: string + label: External Database Port + group: "Database Settings" + show_if: "postgresql.enabled=false" +- variable: database.user + default: "" + description: "External database username" + type: string + label: External Database Username + group: "Database Settings" + show_if: "postgresql.enabled=false" +- variable: database.password + default: "" + description: "External database password" + type: password + label: External Database Password + group: "Database Settings" + show_if: "postgresql.enabled=false" + +# Advance Settings +- variable: advancedOptions + default: false + description: "Show advanced configurations" + label: Show Advanced Configurations + type: boolean + show_subquestion_if: true + group: "Advanced Options" + subquestions: + - variable: artifactory.primary.resources.requests.cpu + default: "500m" + description: "Artifactory primary node initial cpu request" + type: string + label: Artifactory Primary Node Initial CPU Request + - variable: artifactory.primary.resources.requests.memory + default: "1Gi" + description: "Artifactory primary node initial memory request" + type: string + label: Artifactory Primary Node Initial Memory Request + - variable: artifactory.primary.javaOpts.xms + default: "1g" + description: "Artifactory primary node java Xms size" + type: string + label: Artifactory Primary Node Java Xms Size + - variable: artifactory.primary.resources.limits.cpu + default: "2" + description: "Artifactory primary node cpu limit" + type: string + label: Artifactory Primary Node CPU Limit + - variable: artifactory.primary.resources.limits.memory + default: "4Gi" + description: "Artifactory primary node memory limit" + type: string + label: Artifactory Primary Node Memory Limit + - variable: artifactory.primary.javaOpts.xmx + default: "4g" + description: "Artifactory primary node java Xmx size" + type: string + label: Artifactory Primary Node Java Xmx Size + - variable: artifactory.node.resources.requests.cpu + default: "500m" + description: "Artifactory member node initial cpu request" + type: string + label: Artifactory Member Node Initial CPU Request + - variable: artifactory.node.resources.requests.memory + default: "2Gi" + description: "Artifactory member node initial memory request" + type: string + label: Artifactory Member Node Initial Memory Request + - variable: artifactory.node.javaOpts.xms + default: "1g" + description: "Artifactory member node java Xms size" + type: string + label: Artifactory Member Node Java Xms Size + - variable: artifactory.node.resources.limits.cpu + default: "2" + description: "Artifactory member node cpu limit" + type: string + label: Artifactory Member Node CPU Limit + - variable: artifactory.node.resources.limits.memory + default: "4Gi" + description: "Artifactory member node memory limit" + type: string + label: Artifactory Member Node Memory Limit + - variable: artifactory.node.javaOpts.xmx + default: "4g" + description: "Artifactory member node java Xmx size" + type: string + label: Artifactory Member Node Java Xmx Size + +# Internal Settings +- variable: installerInfo + default: '\{\"productId\": \"RancherHelm_artifactory-ha/7.17.5\", \"features\": \[\{\"featureId\": \"Partner/ACC-007246\"\}\]\}' + type: string + group: "Internal Settings (Do not modify)" diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge-extra-config.yaml new file mode 100644 index 0000000000..6afc491dc8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge-extra-config.yaml @@ -0,0 +1,44 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=200 + -Dartifactory.async.poolMaxQueueSize=100000 + -Dartifactory.http.client.max.total.connections=150 + -Dartifactory.http.client.max.connections.per.route=150 + -Dartifactory.access.client.max.connections=200 + -Dartifactory.metadata.event.operator.threads=5 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=1048576 + -XX:MaxDirectMemorySize=1024m + + tomcat: + connector: + maxThreads: 800 + extraConfig: 'acceptCount="1200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 200 + +access: + tomcat: + connector: + maxThreads: 200 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + + database: + maxOpenConnections: 200 + +metadata: + database: + maxOpenConnections: 200 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge.yaml new file mode 100644 index 0000000000..02cf7f94e4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-2xlarge.yaml @@ -0,0 +1,127 @@ +############################################################## +# The 2xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 6 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "4" + memory: 20Gi + limits: + # cpu: "20" + memory: 24Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: "1" + memory: 1Gi + limits: + # cpu: "6" + memory: 2Gi + +frontend: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 1Gi + +metadata: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 2Gi + +event: + resources: + requests: + cpu: 200m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +access: + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 4Gi + +observability: + resources: + requests: + cpu: 200m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +jfconnect: + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + # cpu: "1" + memory: 250Mi + +nginx: + replicaCount: 3 + disableProxyBuffering: true + resources: + requests: + cpu: "4" + memory: "6Gi" + limits: + # cpu: "14" + memory: "8Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "5000" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 256Gi + cpu: "64" + limits: + memory: 256Gi + # cpu: "128" diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large-extra-config.yaml new file mode 100644 index 0000000000..fac24ad687 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large-extra-config.yaml @@ -0,0 +1,44 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=65 + -Dartifactory.async.corePoolSize=80 + -Dartifactory.async.poolMaxQueueSize=20000 + -Dartifactory.http.client.max.total.connections=100 + -Dartifactory.http.client.max.connections.per.route=100 + -Dartifactory.access.client.max.connections=125 + -Dartifactory.metadata.event.operator.threads=4 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=524288 + -XX:MaxDirectMemorySize=512m + + tomcat: + connector: + maxThreads: 500 + extraConfig: 'acceptCount="800" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 100 + +access: + tomcat: + connector: + maxThreads: 125 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + + database: + maxOpenConnections: 100 + +metadata: + database: + maxOpenConnections: 100 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large.yaml new file mode 100644 index 0000000000..504edf1ed8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-large.yaml @@ -0,0 +1,127 @@ +############################################################## +# The large sizing +# This size is intended for large organizations. It can be increased with adding replicas or moving to the xlarge sizing +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 3 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "2" + memory: 10Gi + limits: + # cpu: "14" + memory: 12Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "8" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 3Gi + +router: + resources: + requests: + cpu: 200m + memory: 400Mi + limits: + # cpu: "4" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + # cpu: "4" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "1" + memory: "500Mi" + limits: + # cpu: "4" + memory: "1Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "600" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 64Gi + cpu: "16" + limits: + memory: 64Gi + # cpu: "32" diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium-extra-config.yaml new file mode 100644 index 0000000000..b2b20b198b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium-extra-config.yaml @@ -0,0 +1,45 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=40 + -Dartifactory.async.poolMaxQueueSize=10000 + -Dartifactory.http.client.max.total.connections=50 + -Dartifactory.http.client.max.connections.per.route=50 + -Dartifactory.access.client.max.connections=75 + -Dartifactory.metadata.event.operator.threads=3 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=262144 + -XX:MaxDirectMemorySize=256m + + tomcat: + connector: + maxThreads: 300 + extraConfig: 'acceptCount="600" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 50 + +access: + tomcat: + connector: + maxThreads: 75 + + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + + database: + maxOpenConnections: 50 + +metadata: + database: + maxOpenConnections: 50 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium.yaml new file mode 100644 index 0000000000..93b79788df --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-medium.yaml @@ -0,0 +1,127 @@ +############################################################## +# The medium sizing +# This size is just 2 replicas of the small size. Vertical sizing of all services is not changed +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 2 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + # cpu: "10" + memory: 5Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + # cpu: "1" + memory: 500Mi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +access: + resources: + requests: + cpu: 1 + memory: 1.5Gi + limits: + # cpu: 1.5 + memory: 2Gi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "100m" + memory: "100Mi" + limits: + # cpu: "2" + memory: "500Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "200" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 32Gi + cpu: "8" + limits: + memory: 32Gi + # cpu: "16" \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small-extra-config.yaml new file mode 100644 index 0000000000..e8329f1a3e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small-extra-config.yaml @@ -0,0 +1,43 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=40 + -Dartifactory.async.poolMaxQueueSize=10000 + -Dartifactory.http.client.max.total.connections=50 + -Dartifactory.http.client.max.connections.per.route=50 + -Dartifactory.access.client.max.connections=75 + -Dartifactory.metadata.event.operator.threads=3 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=262144 + -XX:MaxDirectMemorySize=256m + + tomcat: + connector: + maxThreads: 300 + extraConfig: 'acceptCount="600" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 50 + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + +metadata: + database: + maxOpenConnections: 50 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small.yaml new file mode 100644 index 0000000000..b75a22323f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-small.yaml @@ -0,0 +1,127 @@ +############################################################## +# The small sizing +# This is the size recommended for running Artifactory for small teams +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 1 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + # cpu: "10" + memory: 5Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + # cpu: "1" + memory: 500Mi + +access: + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 1 + disableProxyBuffering: true + resources: + requests: + cpu: "100m" + memory: "100Mi" + limits: + # cpu: "2" + memory: "500Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "100" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 16Gi + cpu: "4" + limits: + memory: 16Gi + # cpu: "10" diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge-extra-config.yaml new file mode 100644 index 0000000000..8d04850ad8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge-extra-config.yaml @@ -0,0 +1,42 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=65 + -Dartifactory.async.corePoolSize=160 + -Dartifactory.async.poolMaxQueueSize=50000 + -Dartifactory.http.client.max.total.connections=150 + -Dartifactory.http.client.max.connections.per.route=150 + -Dartifactory.access.client.max.connections=150 + -Dartifactory.metadata.event.operator.threads=5 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=1048576 + -XX:MaxDirectMemorySize=1024m + tomcat: + connector: + maxThreads: 600 + extraConfig: 'acceptCount="1200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 150 + +access: + tomcat: + connector: + maxThreads: 150 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 150 + +metadata: + database: + maxOpenConnections: 150 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge.yaml new file mode 100644 index 0000000000..550bd051d6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xlarge.yaml @@ -0,0 +1,127 @@ +############################################################## +# The xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 4 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "2" + memory: 14Gi + limits: + # cpu: "14" + memory: 16Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 4Gi + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "4" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + # cpu: "4" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "4" + memory: "4Gi" + limits: + # cpu: "12" + memory: "8Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "2000" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 128Gi + cpu: "32" + limits: + memory: 128Gi + # cpu: "64" diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall-extra-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall-extra-config.yaml new file mode 100644 index 0000000000..1371e87b8d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall-extra-config.yaml @@ -0,0 +1,43 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + primary: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=10 + -Dartifactory.async.poolMaxQueueSize=2000 + -Dartifactory.http.client.max.total.connections=20 + -Dartifactory.http.client.max.connections.per.route=20 + -Dartifactory.access.client.max.connections=15 + -Dartifactory.metadata.event.operator.threads=2 + -XX:MaxMetaspaceSize=400m + -XX:CompressedClassSpaceSize=96m + -Djdk.nio.maxCachedBufferSize=131072 + -XX:MaxDirectMemorySize=128m + tomcat: + connector: + maxThreads: 50 + extraConfig: 'acceptCount="200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 15 + +access: + tomcat: + connector: + maxThreads: 15 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 15 + +metadata: + database: + maxOpenConnections: 15 + diff --git a/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall.yaml b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall.yaml new file mode 100644 index 0000000000..3f7b07138b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/sizing/artifactory-xsmall.yaml @@ -0,0 +1,127 @@ +############################################################## +# The xsmall sizing +# This is the minimum size recommended for running Artifactory +############################################################## +splitServicesToContainers: true +artifactory: + primary: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 1 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 3Gi + limits: + # cpu: "10" + memory: 4Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +frontend: + resources: + requests: + cpu: 50m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 1 + disableProxyBuffering: true + resources: + requests: + cpu: "50m" + memory: "50Mi" + limits: + # cpu: "1" + memory: "250Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "50" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 8Gi + cpu: "2" + limits: + memory: 8Gi + # cpu: "8" \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/NOTES.txt b/charts/jfrog/artifactory-ha/107.90.9/templates/NOTES.txt new file mode 100644 index 0000000000..30dfab8b8a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/NOTES.txt @@ -0,0 +1,149 @@ +Congratulations. You have just deployed JFrog Artifactory HA! + +{{- if .Values.artifactory.masterKey }} +{{- if and (not .Values.artifactory.masterKeySecretName) (eq .Values.artifactory.masterKey "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") }} + + +***************************************** WARNING ****************************************** +* Your Artifactory master key is still set to the provided example: * +* artifactory.masterKey=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF * +* * +* You should change this to your own generated key: * +* $ export MASTER_KEY=$(openssl rand -hex 32) * +* $ echo ${MASTER_KEY} * +* * +* Pass the created master key to helm with '--set artifactory.masterKey=${MASTER_KEY}' * +* * +* Alternatively, you can use a pre-existing secret with a key called master-key with * +* '--set artifactory.masterKeySecretName=${SECRET_NAME}' * +******************************************************************************************** +{{- end }} +{{- end }} + +{{- if .Values.artifactory.joinKey }} +{{- if eq .Values.artifactory.joinKey "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" }} + + +***************************************** WARNING ****************************************** +* Your Artifactory join key is still set to the provided example: * +* artifactory.joinKey=EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE * +* * +* You should change this to your own generated key: * +* $ export JOIN_KEY=$(openssl rand -hex 32) * +* $ echo ${JOIN_KEY} * +* * +* Pass the created master key to helm with '--set artifactory.joinKey=${JOIN_KEY}' * +* * +******************************************************************************************** +{{- end }} +{{- end }} + + +{{- if .Values.artifactory.setSecurityContext }} +****************************************** WARNING ********************************************** +* From chart version 107.84.x, `setSecurityContext` has been renamed to `podSecurityContext`, * + please change your values.yaml before upgrade , For more Info , refer to 107.84.x changelog * +************************************************************************************************* +{{- end }} + +{{- if and (or (or (or (or (or ( or ( or ( or (or (or ( or (or .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName) .Values.systemYamlOverride.existingSecret) (or .Values.artifactory.customCertificates.enabled .Values.global.customCertificates.enabled)) .Values.aws.licenseConfigSecretName) .Values.artifactory.persistence.customBinarystoreXmlSecret) .Values.access.customCertificatesSecretName) .Values.systemYamlOverride.existingSecret) .Values.artifactory.license.secret) .Values.artifactory.userPluginSecrets) (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey)) (and .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName)) (or .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName)) .Values.artifactory.unifiedSecretInstallation }} +****************************************** WARNING ************************************************************************************************** +* The unifiedSecretInstallation flag is currently enabled, which creates the unified secret. The existing secrets will continue as separate secrets.* +* Update the values.yaml with the existing secrets to add them to the unified secret. * +***************************************************************************************************************************************************** +{{- end }} + +{{- if .Values.postgresql.enabled }} + +DATABASE: +To extract the database password, run the following +export DB_PASSWORD=$(kubectl get --namespace {{ .Release.Namespace }} $(kubectl get secret --namespace {{ .Release.Namespace }} -o name | grep postgresql) -o jsonpath="{.data.postgresql-password}" | base64 --decode) +echo ${DB_PASSWORD} +{{- end }} + +SETUP: +1. Get the Artifactory IP and URL + + {{- if contains "NodePort" .Values.nginx.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "artifactory-ha.nginx.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT/ + + {{- else if contains "LoadBalancer" .Values.nginx.service.type }} + NOTE: It may take a few minutes for the LoadBalancer public IP to be available! + + You can watch the status of the service by running 'kubectl get svc -w {{ template "artifactory-ha.nginx.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "artifactory-ha.nginx.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP/ + + {{- else if contains "ClusterIP" .Values.nginx.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "component={{ .Values.nginx.name }}" -o jsonpath="{.items[0].metadata.name}") + kubectl port-forward --namespace {{ .Release.Namespace }} $POD_NAME 8080:80 + echo http://127.0.0.1:8080 + + {{- end }} + +2. Open Artifactory in your browser + Default credential for Artifactory: + user: admin + password: password + + {{- if .Values.artifactory.license.secret }} + +3. Artifactory license(s) is deployed as a Kubernetes secret. This method is relevant for initial deployment only! + Updating the license should be done via Artifactory UI or REST API. If you want to keep managing the artifactory license using the same method, you can use artifactory.copyOnEveryStartup in values.yaml. + + {{- else }} + +3. Add HA licenses to activate Artifactory HA through the Artifactory UI + NOTE: Each Artifactory node requires a valid license. See https://www.jfrog.com/confluence/display/RTF/HA+Installation+and+Setup for more details. + + {{- end }} + +{{ if or .Values.artifactory.primary.javaOpts.jmx.enabled .Values.artifactory.node.javaOpts.jmx.enabled }} +JMX configuration: +{{- if not (contains "LoadBalancer" .Values.artifactory.service.type) }} +If you want to access JMX from you computer with jconsole, you should set ".Values.artifactory.service.type=LoadBalancer" !!! +{{ end }} + +1. Get the Artifactory service IP: +{{- if .Values.artifactory.primary.javaOpts.jmx.enabled }} +export PRIMARY_SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "artifactory-ha.primary.name" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +{{- end }} +{{- if .Values.artifactory.node.javaOpts.jmx.enabled }} +export MEMBER_SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "artifactory-ha.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +{{- end }} + +2. Map the service name to the service IP in /etc/hosts: +{{- if .Values.artifactory.primary.javaOpts.jmx.enabled }} +sudo sh -c "echo \"${PRIMARY_SERVICE_IP} {{ template "artifactory-ha.primary.name" . }}\" >> /etc/hosts" +{{- end }} +{{- if .Values.artifactory.node.javaOpts.jmx.enabled }} +sudo sh -c "echo \"${MEMBER_SERVICE_IP} {{ template "artifactory-ha.fullname" . }}\" >> /etc/hosts" +{{- end }} + +3. Launch jconsole: +{{- if .Values.artifactory.primary.javaOpts.jmx.enabled }} +jconsole {{ template "artifactory-ha.primary.name" . }}:{{ .Values.artifactory.primary.javaOpts.jmx.port }} +{{- end }} +{{- if .Values.artifactory.node.javaOpts.jmx.enabled }} +jconsole {{ template "artifactory-ha.fullname" . }}:{{ .Values.artifactory.node.javaOpts.jmx.port }} +{{- end }} +{{- end }} + + +{{- if ge (.Values.artifactory.node.replicaCount | int) 1 }} +***************************************** WARNING ***************************************************************************** +* Currently member node(s) are enabled, will be deprecated in upcoming releases * +* It is recommended to upgrade from primary-members to primary-only. * +* It can be done by deploying the chart ( >=107.59.x) with the new values. Also, please refer to changelog of 107.59.x chart * +* More Info: https://jfrog.com/help/r/jfrog-installation-setup-documentation/cloud-native-high-availability * +******************************************************************************************************************************* +{{- end }} + +{{- if and .Values.nginx.enabled .Values.ingress.hosts }} +***************************************** WARNING ***************************************************************************** +* when nginx is enabled , .Values.ingress.hosts will be deprecated in upcoming releases * +* It is recommended to use nginx.hosts instead ingress.hosts +******************************************************************************************************************************* +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/_helpers.tpl b/charts/jfrog/artifactory-ha/107.90.9/templates/_helpers.tpl new file mode 100644 index 0000000000..d6fb229fed --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/_helpers.tpl @@ -0,0 +1,563 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "artifactory-ha.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The primary node name +*/}} +{{- define "artifactory-ha.primary.name" -}} +{{- if .Values.nameOverride -}} +{{- printf "%s-primary" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := .Release.Name | trunc 29 -}} +{{- printf "%s-%s-primary" $name .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +The member node name +*/}} +{{- define "artifactory-ha.node.name" -}} +{{- if .Values.nameOverride -}} +{{- printf "%s-member" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := .Release.Name | trunc 29 -}} +{{- printf "%s-%s-member" $name .Chart.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Expand the name nginx service. +*/}} +{{- define "artifactory-ha.nginx.name" -}} +{{- default .Values.nginx.name .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 "artifactory-ha.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{/* +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 "artifactory-ha.nginx.fullname" -}} +{{- if .Values.nginx.fullnameOverride -}} +{{- .Values.nginx.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nginx.name -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "artifactory-ha.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{ default (include "artifactory-ha.fullname" .) .Values.serviceAccount.name }} +{{- else -}} +{{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "artifactory-ha.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Generate SSL certificates +*/}} +{{- define "artifactory-ha.gen-certs" -}} +{{- $altNames := list ( printf "%s.%s" (include "artifactory-ha.fullname" .) .Release.Namespace ) ( printf "%s.%s.svc" (include "artifactory-ha.fullname" .) .Release.Namespace ) -}} +{{- $ca := genCA "artifactory-ca" 365 -}} +{{- $cert := genSignedCert ( include "artifactory-ha.fullname" . ) nil $altNames 365 $ca -}} +tls.crt: {{ $cert.Cert | b64enc }} +tls.key: {{ $cert.Key | b64enc }} +{{- end -}} + +{{/* +Scheme (http/https) based on Access or Router TLS enabled/disabled +*/}} +{{- define "artifactory-ha.scheme" -}} +{{- if or .Values.access.accessConfig.security.tls .Values.router.tlsEnabled -}} +{{- printf "%s" "https" -}} +{{- else -}} +{{- printf "%s" "http" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve joinKey value +*/}} +{{- define "artifactory-ha.joinKey" -}} +{{- if .Values.global.joinKey -}} +{{- .Values.global.joinKey -}} +{{- else if .Values.artifactory.joinKey -}} +{{- .Values.artifactory.joinKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve jfConnectToken value +*/}} +{{- define "artifactory-ha.jfConnectToken" -}} +{{- .Values.artifactory.jfConnectToken -}} +{{- end -}} + +{{/* +Resolve masterKey value +*/}} +{{- define "artifactory-ha.masterKey" -}} +{{- if .Values.global.masterKey -}} +{{- .Values.global.masterKey -}} +{{- else if .Values.artifactory.masterKey -}} +{{- .Values.artifactory.masterKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve joinKeySecretName value +*/}} +{{- define "artifactory-ha.joinKeySecretName" -}} +{{- if .Values.global.joinKeySecretName -}} +{{- .Values.global.joinKeySecretName -}} +{{- else if .Values.artifactory.joinKeySecretName -}} +{{- .Values.artifactory.joinKeySecretName -}} +{{- else -}} +{{ include "artifactory-ha.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve jfConnectTokenSecretName value +*/}} +{{- define "artifactory-ha.jfConnectTokenSecretName" -}} +{{- if .Values.artifactory.jfConnectTokenSecretName -}} +{{- .Values.artifactory.jfConnectTokenSecretName -}} +{{- else -}} +{{ include "artifactory-ha.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve masterKeySecretName value +*/}} +{{- define "artifactory-ha.masterKeySecretName" -}} +{{- if .Values.global.masterKeySecretName -}} +{{- .Values.global.masterKeySecretName -}} +{{- else if .Values.artifactory.masterKeySecretName -}} +{{- .Values.artifactory.masterKeySecretName -}} +{{- else -}} +{{ include "artifactory-ha.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve imagePullSecrets value +*/}} +{{- define "artifactory-ha.imagePullSecrets" -}} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if .Values.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainersBegin value +*/}} +{{- define "artifactory-ha.customInitContainersBegin" -}} +{{- if .Values.global.customInitContainersBegin -}} +{{- .Values.global.customInitContainersBegin -}} +{{- end -}} +{{- if .Values.artifactory.customInitContainersBegin -}} +{{- .Values.artifactory.customInitContainersBegin -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainers value +*/}} +{{- define "artifactory-ha.customInitContainers" -}} +{{- if .Values.global.customInitContainers -}} +{{- .Values.global.customInitContainers -}} +{{- end -}} +{{- if .Values.artifactory.customInitContainers -}} +{{- .Values.artifactory.customInitContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumes value +*/}} +{{- define "artifactory-ha.customVolumes" -}} +{{- if .Values.global.customVolumes -}} +{{- .Values.global.customVolumes -}} +{{- end -}} +{{- if .Values.artifactory.customVolumes -}} +{{- .Values.artifactory.customVolumes -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve unifiedCustomSecretVolumeName value +*/}} +{{- define "artifactory-ha.unifiedCustomSecretVolumeName" -}} +{{- printf "%s-%s" (include "artifactory-ha.name" .) ("unified-secret-volume") | trunc 63 -}} +{{- end -}} + +{{/* +Check the Duplication of volume names for secrets. If unifiedSecretInstallation is enabled then the method is checking for volume names, +if the volume exists in customVolume then an extra volume with the same name will not be getting added in unifiedSecretInstallation case.*/}} +{{- define "artifactory-ha.checkDuplicateUnifiedCustomVolume" -}} +{{- if or .Values.global.customVolumes .Values.artifactory.customVolumes -}} +{{- $val := (tpl (include "artifactory-ha.customVolumes" .) .) | toJson -}} +{{- contains (include "artifactory-ha.unifiedCustomSecretVolumeName" .) $val | toString -}} +{{- else -}} +{{- printf "%s" "false" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumeMounts value +*/}} +{{- define "artifactory-ha.customVolumeMounts" -}} +{{- if .Values.global.customVolumeMounts -}} +{{- .Values.global.customVolumeMounts -}} +{{- end -}} +{{- if .Values.artifactory.customVolumeMounts -}} +{{- .Values.artifactory.customVolumeMounts -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customSidecarContainers value +*/}} +{{- define "artifactory-ha.customSidecarContainers" -}} +{{- if .Values.global.customSidecarContainers -}} +{{- .Values.global.customSidecarContainers -}} +{{- end -}} +{{- if .Values.artifactory.customSidecarContainers -}} +{{- .Values.artifactory.customSidecarContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory chart image names +*/}} +{{- define "artifactory-ha.getImageInfoByValue" -}} +{{- $dot := index . 0 }} +{{- $indexReference := index . 1 }} +{{- $registryName := index $dot.Values $indexReference "image" "registry" -}} +{{- $repositoryName := index $dot.Values $indexReference "image" "repository" -}} +{{- $tag := "" -}} +{{- if and (eq $indexReference "artifactory") (hasKey $dot.Values "artifactoryService") }} + {{- if default false $dot.Values.artifactoryService.enabled }} + {{- $indexReference = "artifactoryService" -}} + {{- $tag = default $dot.Chart.Annotations.artifactoryServiceVersion (index $dot.Values $indexReference "image" "tag") | toString -}} + {{- $repositoryName = index $dot.Values $indexReference "image" "repository" -}} + {{- else -}} + {{- $tag = default $dot.Chart.AppVersion (index $dot.Values $indexReference "image" "tag") | toString -}} + {{- end -}} +{{- else -}} + {{- $tag = default $dot.Chart.AppVersion (index $dot.Values $indexReference "image" "tag") | toString -}} +{{- end -}} +{{- if $dot.Values.global }} + {{- if and $dot.Values.splitServicesToContainers $dot.Values.global.versions.router (eq $indexReference "router") }} + {{- $tag = $dot.Values.global.versions.router | toString -}} + {{- end -}} + {{- if and $dot.Values.global.versions.initContainers (eq $indexReference "initContainers") }} + {{- $tag = $dot.Values.global.versions.initContainers | toString -}} + {{- end -}} + {{- if $dot.Values.global.versions.artifactory }} + {{- if or (eq $indexReference "artifactory") (eq $indexReference "metadata") (eq $indexReference "nginx") (eq $indexReference "observability") }} + {{- $tag = $dot.Values.global.versions.artifactory | toString -}} + {{- end -}} + {{- end -}} + {{- if $dot.Values.global.imageRegistry }} + {{- printf "%s/%s:%s" $dot.Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory app version +*/}} +{{- define "artifactory-ha.app.version" -}} +{{- $tag := (splitList ":" ((include "artifactory-ha.getImageInfoByValue" (list . "artifactory" )))) | last | toString -}} +{{- printf "%s" $tag -}} +{{- end -}} + +{{/* +Custom certificate copy command +*/}} +{{- define "artifactory-ha.copyCustomCerts" -}} +echo "Copy custom certificates to {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted"; +mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted; +for file in $(ls -1 /tmp/certs/* | grep -v .key | grep -v ":" | grep -v grep); do if [ -f "${file}" ]; then cp -v ${file} {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted; fi done; +if [ -f {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/tls.crt ]; then mv -v {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/tls.crt {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/ca.crt; fi; +{{- end -}} + +{{/* +Circle of trust certificates copy command +*/}} +{{- define "artifactory.copyCircleOfTrustCertsCerts" -}} +echo "Copy circle of trust certificates to {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted"; +mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; +for file in $(ls -1 /tmp/circleoftrustcerts/* | grep -v .key | grep -v ":" | grep -v grep); do if [ -f "${file}" ]; then cp -v ${file} {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; fi done; +{{- end -}} + +{{/* +Resolve requiredServiceTypes value +*/}} +{{- define "artifactory-ha.router.requiredServiceTypes" -}} +{{- $requiredTypes := "jfrt,jfac" -}} +{{- if not .Values.access.enabled -}} + {{- $requiredTypes = "jfrt" -}} +{{- end -}} +{{- if .Values.observability.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfob" -}} +{{- end -}} +{{- if .Values.metadata.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfmd" -}} +{{- end -}} +{{- if .Values.event.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfevt" -}} +{{- end -}} +{{- if .Values.frontend.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jffe" -}} +{{- end -}} +{{- if .Values.jfconnect.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfcon" -}} +{{- end -}} +{{- if .Values.evidence.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfevd" -}} +{{- end -}} +{{- if .Values.mc.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfmc" -}} +{{- end -}} +{{- $requiredTypes -}} +{{- end -}} + +{{/* +nginx scheme (http/https) +*/}} +{{- define "nginx.scheme" -}} +{{- if .Values.nginx.http.enabled -}} +{{- printf "%s" "http" -}} +{{- else -}} +{{- printf "%s" "https" -}} +{{- end -}} +{{- end -}} + + +{{/* +nginx command +*/}} +{{- define "nginx.command" -}} +{{- if .Values.nginx.customCommand }} +{{ toYaml .Values.nginx.customCommand }} +{{- end }} +{{- end -}} + +{{/* +nginx port (8080/8443) based on http/https enabled +*/}} +{{- define "nginx.port" -}} +{{- if .Values.nginx.http.enabled -}} +{{- .Values.nginx.http.internalPort -}} +{{- else -}} +{{- .Values.nginx.https.internalPort -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainers value +*/}} +{{- define "artifactory.nginx.customInitContainers" -}} +{{- if .Values.nginx.customInitContainers -}} +{{- .Values.nginx.customInitContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumes value +*/}} +{{- define "artifactory.nginx.customVolumes" -}} +{{- if .Values.nginx.customVolumes -}} +{{- .Values.nginx.customVolumes -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumeMounts nginx value +*/}} +{{- define "artifactory.nginx.customVolumeMounts" -}} +{{- if .Values.nginx.customVolumeMounts -}} +{{- .Values.nginx.customVolumeMounts -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customSidecarContainers value +*/}} +{{- define "artifactory.nginx.customSidecarContainers" -}} +{{- if .Values.nginx.customSidecarContainers -}} +{{- .Values.nginx.customSidecarContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve Artifactory pod primary node selector value +*/}} +{{- define "artifactory.nodeSelector" -}} +nodeSelector: +{{- if .Values.global.nodeSelector }} +{{ toYaml .Values.global.nodeSelector | indent 2 }} +{{- else if .Values.artifactory.primary.nodeSelector }} +{{ toYaml .Values.artifactory.primary.nodeSelector | indent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Resolve Artifactory pod node nodeselector value +*/}} +{{- define "artifactory.node.nodeSelector" -}} +nodeSelector: +{{- if .Values.global.nodeSelector }} +{{ toYaml .Values.global.nodeSelector | indent 2 }} +{{- else if .Values.artifactory.node.nodeSelector }} +{{ toYaml .Values.artifactory.node.nodeSelector | indent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Resolve Nginx pods node selector value +*/}} +{{- define "nginx.nodeSelector" -}} +nodeSelector: +{{- if .Values.global.nodeSelector }} +{{ toYaml .Values.global.nodeSelector | indent 2 }} +{{- else if .Values.nginx.nodeSelector }} +{{ toYaml .Values.nginx.nodeSelector | indent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Calculate the systemYaml from structured and unstructured text input +*/}} +{{- define "artifactory.finalSystemYaml" -}} +{{ tpl (mergeOverwrite (include "artifactory.systemYaml" . | fromYaml) .Values.artifactory.extraSystemYaml | toYaml) . }} +{{- end -}} + +{{/* +Calculate the systemYaml from the unstructured text input +*/}} +{{- define "artifactory.systemYaml" -}} +{{ include (print $.Template.BasePath "/_system-yaml-render.tpl") . }} +{{- end -}} + +{{/* +Metrics enabled +*/}} +{{- define "metrics.enabled" -}} +shared: + metrics: + enabled: true +{{- end }} + +{{/* +Resolve artifactory metrics +*/}} +{{- define "artifactory.metrics" -}} +{{- if .Values.artifactory.openMetrics -}} +{{- if .Values.artifactory.openMetrics.enabled -}} +{{ include "metrics.enabled" . }} +{{- if .Values.artifactory.openMetrics.filebeat }} +{{- if .Values.artifactory.openMetrics.filebeat.enabled }} +{{ include "metrics.enabled" . }} + filebeat: +{{ tpl (.Values.artifactory.openMetrics.filebeat | toYaml) . | indent 6 }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- else if .Values.artifactory.metrics -}} +{{- if .Values.artifactory.metrics.enabled -}} +{{ include "metrics.enabled" . }} +{{- if .Values.artifactory.metrics.filebeat }} +{{- if .Values.artifactory.metrics.filebeat.enabled }} +{{ include "metrics.enabled" . }} + filebeat: +{{ tpl (.Values.artifactory.metrics.filebeat | toYaml) . | indent 6 }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve unified secret prepend release name +*/}} +{{- define "artifactory.unifiedSecretPrependReleaseName" -}} +{{- if .Values.artifactory.unifiedSecretPrependReleaseName }} +{{- printf "%s" (include "artifactory-ha.fullname" .) -}} +{{- else }} +{{- printf "%s" (include "artifactory-ha.name" .) -}} +{{- end }} +{{- end }} + +{{/* +Resolve nginx hosts value +*/}} +{{- define "artifactory.nginx.hosts" -}} +{{- if .Values.ingress.hosts }} +{{- range .Values.ingress.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} +{{- end -}} +{{- else if .Values.nginx.hosts }} +{{- range .Values.nginx.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/_system-yaml-render.tpl b/charts/jfrog/artifactory-ha/107.90.9/templates/_system-yaml-render.tpl new file mode 100644 index 0000000000..deaa773ea9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/_system-yaml-render.tpl @@ -0,0 +1,5 @@ +{{- if .Values.artifactory.systemYaml -}} +{{- tpl .Values.artifactory.systemYaml . -}} +{{- else -}} +{{ (tpl ( $.Files.Get "files/system.yaml" ) .) }} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/additional-resources.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/additional-resources.yaml new file mode 100644 index 0000000000..c4d06f08ad --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/additional-resources.yaml @@ -0,0 +1,3 @@ +{{ if .Values.additionalResources }} +{{ tpl .Values.additionalResources . }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/admin-bootstrap-creds.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/admin-bootstrap-creds.yaml new file mode 100644 index 0000000000..40d91f75e9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/admin-bootstrap-creds.yaml @@ -0,0 +1,15 @@ +{{- if not (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} +{{- if and .Values.artifactory.admin.password (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-bootstrap-creds + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + bootstrap.creds: {{ (printf "%s@%s=%s" .Values.artifactory.admin.username .Values.artifactory.admin.ip .Values.artifactory.admin.password) | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-access-config.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-access-config.yaml new file mode 100644 index 0000000000..0b96a337d4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-access-config.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.access.accessConfig (not .Values.artifactory.unifiedSecretInstallation) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory-ha.fullname" . }}-access-config + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +type: Opaque +stringData: + access.config.patch.yml: | +{{ tpl (toYaml .Values.access.accessConfig) . | indent 4 }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-binarystore-secret.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-binarystore-secret.yaml new file mode 100644 index 0000000000..6824fe90f8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-binarystore-secret.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.artifactory.persistence.customBinarystoreXmlSecret) (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-binarystore + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +stringData: + binarystore.xml: |- +{{- if .Values.artifactory.persistence.binarystoreXml }} +{{ tpl .Values.artifactory.persistence.binarystoreXml . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/binarystore.xml" ) . | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-configmaps.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-configmaps.yaml new file mode 100644 index 0000000000..1385bc5782 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-configmaps.yaml @@ -0,0 +1,13 @@ +{{ if .Values.artifactory.configMaps }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory-ha.fullname" . }}-configmaps + labels: + app: {{ template "artifactory-ha.fullname" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: +{{ tpl .Values.artifactory.configMaps . | indent 2 }} +{{ end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-custom-secrets.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-custom-secrets.yaml new file mode 100644 index 0000000000..8065fe6865 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-custom-secrets.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.artifactory.customSecrets (not .Values.artifactory.unifiedSecretInstallation) }} +{{- range .Values.artifactory.customSecrets }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory-ha.fullname" $ }}-{{ .name }} + labels: + app: "{{ template "artifactory-ha.name" $ }}" + chart: "{{ template "artifactory-ha.chart" $ }}" + component: "{{ $.Values.artifactory.name }}" + heritage: {{ $.Release.Service | quote }} + release: {{ $.Release.Name | quote }} +type: Opaque +stringData: + {{ .key }}: | +{{ .data | indent 4 -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-database-secrets.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-database-secrets.yaml new file mode 100644 index 0000000000..6daf5db7bc --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-database-secrets.yaml @@ -0,0 +1,24 @@ +{{- if and (not .Values.database.secrets) (not .Values.postgresql.enabled) (not .Values.artifactory.unifiedSecretInstallation) }} +{{- if or .Values.database.url .Values.database.user .Values.database.password }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory-ha.fullname" . }}-database-creds + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +type: Opaque +data: + {{- with .Values.database.url }} + db-url: {{ tpl . $ | b64enc | quote }} + {{- end }} + {{- with .Values.database.user }} + db-user: {{ tpl . $ | b64enc | quote }} + {{- end }} + {{- with .Values.database.password }} + db-password: {{ tpl . $ | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-gcp-credentials-secret.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-gcp-credentials-secret.yaml new file mode 100644 index 0000000000..d90769595b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-gcp-credentials-secret.yaml @@ -0,0 +1,16 @@ +{{- if not .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} +{{- if and (.Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled) (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-gcpcreds + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +stringData: + gcp.credentials.json: |- +{{ tpl .Values.artifactory.persistence.googleStorage.gcpServiceAccount.config . | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-installer-info.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-installer-info.yaml new file mode 100644 index 0000000000..0dff9dc86f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-installer-info.yaml @@ -0,0 +1,16 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-installer-info + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + installer-info.json: | +{{- if .Values.installerInfo -}} +{{- tpl .Values.installerInfo . | nindent 4 -}} +{{- else -}} +{{ (tpl ( .Files.Get "files/installer-info.json" | nindent 4 ) .) }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-license-secret.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-license-secret.yaml new file mode 100644 index 0000000000..73f900863b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-license-secret.yaml @@ -0,0 +1,16 @@ +{{ if and (not .Values.artifactory.unifiedSecretInstallation) (not .Values.artifactory.license.secret) (not .Values.artifactory.license.licenseKey) }} +{{- with .Values.artifactory.license.licenseKey }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory-ha.fullname" $ }}-license + labels: + app: {{ template "artifactory-ha.name" $ }} + chart: {{ template "artifactory-ha.chart" $ }} + heritage: {{ $.Release.Service }} + release: {{ $.Release.Name }} +type: Opaque +data: + artifactory.lic: {{ . | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-migration-scripts.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-migration-scripts.yaml new file mode 100644 index 0000000000..fe40f980fc --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-migration-scripts.yaml @@ -0,0 +1,18 @@ +{{- if .Values.artifactory.migration.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory-ha.fullname" . }}-migration-scripts + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + migrate.sh: | +{{ .Files.Get "files/migrate.sh" | indent 4 }} + migrationHelmInfo.yaml: | +{{ .Files.Get "files/migrationHelmInfo.yaml" | indent 4 }} + migrationStatus.sh: | +{{ .Files.Get "files/migrationStatus.sh" | indent 4 }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-networkpolicy.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-networkpolicy.yaml new file mode 100644 index 0000000000..9924448f04 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-networkpolicy.yaml @@ -0,0 +1,34 @@ +{{- range .Values.networkpolicy }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "artifactory-ha.fullname" $ }}-{{ .name }}-networkpolicy + labels: + app: {{ template "artifactory-ha.name" $ }} + chart: {{ template "artifactory-ha.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} +spec: +{{- if .podSelector }} + podSelector: +{{ .podSelector | toYaml | trimSuffix "\n" | indent 4 -}} +{{ else }} + podSelector: {} +{{- end }} + policyTypes: + {{- if .ingress }} + - Ingress + {{- end }} + {{- if .egress }} + - Egress + {{- end }} +{{- if .ingress }} + ingress: +{{ .ingress | toYaml | trimSuffix "\n" | indent 2 -}} +{{- end }} +{{- if .egress }} + egress: +{{ .egress | toYaml | trimSuffix "\n" | indent 2 -}} +{{- end }} +--- +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-nfs-pvc.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-nfs-pvc.yaml new file mode 100644 index 0000000000..6ed7d82f6b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-nfs-pvc.yaml @@ -0,0 +1,101 @@ +{{- if eq .Values.artifactory.persistence.type "nfs" }} +### Artifactory HA data +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "artifactory-ha.fullname" . }}-data-pv + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + id: {{ template "artifactory-ha.name" . }}-data-pv + type: nfs-volume +spec: + {{- if .Values.artifactory.persistence.nfs.mountOptions }} + mountOptions: +{{ toYaml .Values.artifactory.persistence.nfs.mountOptions | indent 4 }} + {{- end }} + capacity: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + nfs: + server: {{ .Values.artifactory.persistence.nfs.ip }} + path: "{{ .Values.artifactory.persistence.nfs.haDataMount }}" + readOnly: false +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-data-pvc + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + type: nfs-volume +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + selector: + matchLabels: + id: {{ template "artifactory-ha.name" . }}-data-pv + app: {{ template "artifactory-ha.name" . }} + release: {{ .Release.Name }} +--- +### Artifactory HA backup +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "artifactory-ha.fullname" . }}-backup-pv + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + id: {{ template "artifactory-ha.name" . }}-backup-pv + type: nfs-volume +spec: + {{- if .Values.artifactory.persistence.nfs.mountOptions }} + mountOptions: +{{ toYaml .Values.artifactory.persistence.nfs.mountOptions | indent 4 }} + {{- end }} + capacity: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + nfs: + server: {{ .Values.artifactory.persistence.nfs.ip }} + path: "{{ .Values.artifactory.persistence.nfs.haBackupMount }}" + readOnly: false +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "artifactory-ha.fullname" . }}-backup-pvc + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + type: nfs-volume +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + selector: + matchLabels: + id: {{ template "artifactory-ha.name" . }}-backup-pv + app: {{ template "artifactory-ha.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-node-pdb.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-node-pdb.yaml new file mode 100644 index 0000000000..46c6dac21d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/artifactory-node-pdb.yaml @@ -0,0 +1,26 @@ +{{- if gt (.Values.artifactory.node.replicaCount | int) 0 -}} +{{- if .Values.artifactory.node.minAvailable -}} +{{- if semverCompare " + mkdir -p {{ tpl .Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir . }}; + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + volumeMounts: + - mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + name: volume + {{- end }} + {{- end }} + {{- if .Values.artifactory.deleteDBPropertiesOnStartup }} + - name: "delete-db-properties" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'bash' + - '-c' + - 'rm -fv {{ .Values.artifactory.persistence.mountPath }}/etc/db.properties' + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + volumeMounts: + - mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + name: volume + {{- end }} + {{- end }} + {{- if and .Values.artifactory.node.waitForPrimaryStartup.enabled }} + - name: "wait-for-primary" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - 'bash' + - '-c' + - > + echo "Waiting for primary node to be ready..."; + {{- if and .Values.artifactory.node.waitForPrimaryStartup.enabled .Values.artifactory.node.waitForPrimaryStartup.time }} + echo "Sleeping to allow time for primary node to come up"; + sleep {{ .Values.artifactory.node.waitForPrimaryStartup.time }}; + {{- else }} + ready=false; + while ! $ready; do echo Primary not ready. Waiting...; + timeout 2s bash -c " + if [[ -e "{{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml" ]]; then chmod 644 {{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml; fi; + echo "Copy system.yaml to {{ .Values.artifactory.persistence.mountPath }}/etc"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; + {{- if .Values.systemYamlOverride.existingSecret }} + cp -fv /tmp/etc/{{ .Values.systemYamlOverride.dataKey }} {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- else }} + cp -fv /tmp/etc/system.yaml {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- end }} + echo "Copy binarystore.xml file"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory; + cp -fv /tmp/etc/artifactory/binarystore.xml {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory/binarystore.xml; + echo "Removing join.key file"; + rm -fv {{ .Values.artifactory.persistence.mountPath }}/etc/security/join.key; + {{- if .Values.access.resetAccessCAKeys }} + echo "Resetting Access CA Keys - load from database"; + {{- end }} + {{- if .Values.access.customCertificatesSecretName }} + echo "Load custom certificates from database"; + {{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + echo "Copy masterKey to {{ .Values.artifactory.persistence.mountPath }}/etc/security"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security; + echo -n ${ARTIFACTORY_MASTER_KEY} > {{ .Values.artifactory.persistence.mountPath }}/etc/security/master.key; + env: + - name: ARTIFACTORY_MASTER_KEY + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) (or .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName) }} + name: {{ include "artifactory-ha.masterKeySecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: master-key + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + + ######################## SystemYaml ######################### + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.systemYamlOverride.existingSecret }} + - name: systemyaml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + {{- if .Values.systemYamlOverride.existingSecret }} + mountPath: "/tmp/etc/{{.Values.systemYamlOverride.dataKey}}" + subPath: {{ .Values.systemYamlOverride.dataKey }} + {{- else }} + mountPath: "/tmp/etc/system.yaml" + subPath: system.yaml + {{- end }} + + ######################## Binarystore ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## CustomCertificates ########################## + {{- if or .Values.artifactory.customCertificates.enabled .Values.global.customCertificates.enabled }} + - name: copy-custom-certificates + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory-ha.copyCustomCerts" . | indent 10 }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: ca-certs + mountPath: "/tmp/certs" + {{- end }} + + {{- if .Values.artifactory.circleOfTrustCertificatesSecret }} + - name: copy-circle-of-trust-certificates + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory.copyCircleOfTrustCertsCerts" . | indent 10 }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: circle-of-trust-certs + mountPath: "/tmp/circleoftrustcerts" + {{- end }} + + {{- if .Values.waitForDatabase }} + {{- if or .Values.postgresql.enabled }} + - name: "wait-for-db" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - /bin/bash + - -c + - | + echo "Waiting for postgresql to come up" + ready=false; + while ! $ready; do echo waiting; + timeout 2s bash -c " + {{- if .Values.artifactory.migration.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.artifactory.migration.preStartCommand . }}; + {{- end }} + scriptsPath="/opt/jfrog/artifactory/app/bin"; + mkdir -p $scriptsPath; + echo "Copy migration scripts and Run migration"; + cp -fv /tmp/migrate.sh $scriptsPath/migrate.sh; + cp -fv /tmp/migrationHelmInfo.yaml $scriptsPath/migrationHelmInfo.yaml; + cp -fv /tmp/migrationStatus.sh $scriptsPath/migrationStatus.sh; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/log; + bash $scriptsPath/migrationStatus.sh {{ include "artifactory-ha.app.version" . }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; + resources: +{{ toYaml .Values.artifactory.node.resources | indent 10 }} + env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} + - name: JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: migration-scripts + mountPath: "/tmp/migrate.sh" + subPath: migrate.sh + - name: migration-scripts + mountPath: "/tmp/migrationHelmInfo.yaml" + subPath: migrationHelmInfo.yaml + - name: migration-scripts + mountPath: "/tmp/migrationStatus.sh" + subPath: migrationStatus.sh + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + {{- end }} + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + +{{- end }} + {{- if .Values.hostAliases }} + hostAliases: +{{ toYaml .Values.hostAliases | indent 6 }} + {{- end }} + containers: + {{- if .Values.splitServicesToContainers }} + - name: {{ .Values.router.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "router") }} + imagePullPolicy: {{ .Values.router.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/router/app/bin/entrypoint-router.sh + {{- with .Values.router.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_ROUTER_TOPOLOGY_LOCAL_REQUIREDSERVICETYPES + value: {{ include "artifactory-ha.router.requiredServiceTypes" . }} +{{- with .Values.router.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - name: http + containerPort: {{ .Values.router.internalPort }} + volumeMounts: + - name: volume + mountPath: {{ .Values.router.persistence.mountPath | quote }} +{{- with .Values.router.customVolumeMounts }} +{{ tpl . $ | indent 8 }} +{{- end }} + resources: +{{ toYaml .Values.router.resources | indent 10 }} + {{- if .Values.router.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.router.startupProbe.config . | indent 10 }} + {{- end }} +{{- if .Values.router.readinessProbe.enabled }} + readinessProbe: +{{ tpl .Values.router.readinessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.router.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.router.livenessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.enabled }} + - name: {{ .Values.frontend.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/third-party/node/bin/node /opt/jfrog/artifactory/app/frontend/bin/server/dist/bundle.js /opt/jfrog/artifactory/app/frontend + {{- with .Values.frontend.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name : JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.frontend.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.frontend.resources | indent 10 }} + {{- if .Values.frontend.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.frontend.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.frontend.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.evidence.enabled }} + - name: {{ .Values.evidence.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/evidence/bin/jf-evidence start + {{- with .Values.evidence.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.evidence.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.evidence.internalPort }} + name: http-evidence + - containerPort: {{ .Values.evidence.externalPort }} + name: grpc-evidence + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.evidence.resources | indent 10 }} + {{- if .Values.evidence.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.evidence.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.evidence.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.evidence.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.metadata.enabled }} + - name: {{ .Values.metadata.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "metadata") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/metadata/bin/jf-metadata start + {{- with .Values.metadata.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.metadata.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.metadata.resources | indent 10 }} + {{- if .Values.metadata.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.metadata.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.metadata.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.metadata.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.event.enabled }} + - name: {{ .Values.event.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/event/bin/jf-event start + {{- with .Values.event.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.event.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.event.resources | indent 10 }} + {{- if .Values.event.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.event.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.event.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.event.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.jfconnect.enabled }} + - name: {{ .Values.jfconnect.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/jfconnect/bin/jf-connect start + {{- with .Values.jfconnect.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.jfconnect.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.jfconnect.resources | indent 10 }} + {{- if .Values.jfconnect.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.jfconnect.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.jfconnect.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.jfconnect.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.federation.enabled .Values.federation.embedded }} + - name: {{ .Values.federation.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/third-party/java/bin/java {{ .Values.federation.extraJavaOpts }} -jar /opt/jfrog/artifactory/app/rtfs/lib/jf-rtfs + {{- with .Values.federation.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_RTFS_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} +{{- with .Values.federation.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.federation.resources | indent 10 }} + {{- if .Values.federation.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.federation.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.federation.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.federation.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.observability.enabled }} + - name: {{ .Values.observability.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "observability") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/observability/bin/jf-observability start + {{- with .Values.observability.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.observability.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.observability.resources | indent 10 }} + {{- if .Values.observability.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.observability.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.observability.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.observability.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.access.enabled (not (.Values.access.runOnArtifactoryTomcat | default false)) }} + - name: {{ .Values.access.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.access.resources }} + resources: +{{ toYaml .Values.access.resources | indent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + set -e; + {{- if .Values.access.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.access.preStartCommand . }}; + {{- end }} + exec /opt/jfrog/artifactory/app/access/bin/entrypoint-access.sh + {{- with .Values.access.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.access.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentPodVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: awsmp-product-license + mountPath: "/var/run/secrets/product-license" + {{- end }} + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + + ######################## Artifactory persistence fs ########################## + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + + ######################## Artifactory ConfigMap ########################## + {{- if .Values.artifactory.configMapName }} + - name: bootstrap-config + mountPath: "/bootstrap/" + {{- end }} + + ######################## Artifactory license ########################## + {{- if or .Values.artifactory.license.secret .Values.artifactory.license.licenseKey }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.license.secret }} + - name: artifactory-license + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/artifactory.cluster.license" + {{- if .Values.artifactory.license.secret }} + subPath: {{ .Values.artifactory.license.dataKey }} + {{- else if .Values.artifactory.license.licenseKey }} + subPath: artifactory.lic + {{- end }} + {{- end }} + {{- end }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + {{- if .Values.access.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.access.startupProbe.config . | indent 10 }} + {{- end }} + {{- if semverCompare " + set -e; + {{- range .Values.artifactory.copyOnEveryStartup }} + {{- $targetPath := printf "%s/%s" $.Values.artifactory.persistence.mountPath .target }} + {{- $baseDirectory := regexFind ".*/" $targetPath }} + mkdir -p {{ $baseDirectory }}; + cp -Lrf {{ .source }} {{ $.Values.artifactory.persistence.mountPath }}/{{ .target }}; + {{- end }} + {{- if .Values.artifactory.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.artifactory.preStartCommand . }}; + {{- end }} + {{- with .Values.artifactory.node.preStartCommand }} + echo "Running member node specific custom preStartCommand command"; + {{ tpl . $ }}; + {{- end }} + exec /entrypoint-artifactory.sh + {{- with .Values.artifactory.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if .Values.aws.license.enabled }} + - name: IS_AWS_LICENSE + value: "true" + - name: AWS_REGION + value: {{ .Values.aws.region | quote }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE + value: "/var/run/secrets/product-license/license_token" + - name: AWS_ROLE_ARN + valueFrom: + secretKeyRef: + name: {{ .Values.aws.licenseConfigSecretName }} + key: iam_role + {{- end }} + {{- end }} + {{- if .Values.splitServicesToContainers }} + - name : JF_ROUTER_ENABLED + value: "true" + - name : JF_ROUTER_SERVICE_ENABLED + value: "false" + - name : JF_EVENT_ENABLED + value: "false" + - name : JF_METADATA_ENABLED + value: "false" + - name : JF_FRONTEND_ENABLED + value: "false" + - name: JF_FEDERATION_ENABLED + value: "false" + - name : JF_OBSERVABILITY_ENABLED + value: "false" + - name : JF_JFCONNECT_SERVICE_ENABLED + value: "false" + - name : JF_EVIDENCE_ENABLED + value: "false" + {{- if not (.Values.access.runOnArtifactoryTomcat | default false) }} + - name : JF_ACCESS_ENABLED + value: "false" + {{- end}} + {{- end }} + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} + - name: JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.artifactory.internalPort }} + name: http + - containerPort: {{ .Values.artifactory.internalArtifactoryPort }} + name: http-internal + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- if .Values.artifactory.node.javaOpts.jmx.enabled }} + - containerPort: {{ .Values.artifactory.node.javaOpts.jmx.port }} + name: tcp-jmx + {{- end }} + {{- if .Values.artifactory.ssh.enabled }} + - containerPort: {{ .Values.artifactory.ssh.internalPort }} + name: tcp-ssh + {{- end }} + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentPodVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: awsmp-product-license + mountPath: "/var/run/secrets/product-license" + {{- end }} + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + + ######################## Artifactory persistence fs ########################## + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + + ######################## Artifactory ConfigMap ########################## + {{- if .Values.artifactory.configMapName }} + - name: bootstrap-config + mountPath: "/bootstrap/" + {{- end }} + + ######################## Artifactory license ########################## + {{- if or .Values.artifactory.license.secret .Values.artifactory.license.licenseKey }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.license.secret }} + - name: artifactory-license + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/artifactory.cluster.license" + {{- if .Values.artifactory.license.secret }} + subPath: {{ .Values.artifactory.license.dataKey }} + {{- else if .Values.artifactory.license.licenseKey }} + subPath: artifactory.lic + {{- end }} + {{- end }} + {{- end }} + - name: installer-info + mountPath: "/artifactory_bootstrap/info/installer-info.json" + subPath: installer-info.json + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + resources: +{{ toYaml .Values.artifactory.node.resources | indent 10 }} + {{- if .Values.artifactory.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.artifactory.startupProbe.config . | indent 10 }} + {{- end }} + {{- if and (not .Values.splitServicesToContainers) (semverCompare "= 107.79.x), just set databaseUpgradeReady=true \n" .Values.databaseUpgradeReady | quote }} +{{- end }} +{{- if .Values.artifactory.postStartCommand }} + {{- fail ".Values.artifactory.postStartCommand is not supported and should be replaced with .Values.artifactory.lifecycle.postStart.exec.command" }} +{{- end }} +{{- if eq .Values.artifactory.persistence.type "aws-s3" }} + {{- fail "\nPersistence storage type 'aws-s3' is deprecated and is not supported and should be replaced with 'aws-s3-v3'" }} +{{- end }} +{{- if or .Values.artifactory.persistence.googleStorage.identity .Values.artifactory.persistence.googleStorage.credential }} + {{- fail "\nGCP Bucket Authentication with Identity and Credential is deprecated" }} +{{- end }} +{{- if (eq (.Values.artifactory.setSecurityContext | toString) "false" ) }} + {{- fail "\n You need to set security context at the pod level. .Values.artifactory.setSecurityContext is no longer supported. Replace it with .Values.artifactory.podSecurityContext" }} +{{- end }} +{{- if or .Values.artifactory.uid .Values.artifactory.gid }} +{{- if or (not (eq (.Values.artifactory.uid | toString) "1030" )) (not (eq (.Values.artifactory.gid | toString) "1030" )) }} + {{- fail "\n .Values.artifactory.uid and .Values.artifactory.gid are no longer supported. You need to set these values at the pod security context level. Replace them with .Values.artifactory.podSecurityContext.runAsUser, .Values.artifactory.podSecurityContext.runAsGroup and .Values.artifactory.podSecurityContext.fsGroup" }} +{{- end }} +{{- end }} +{{- if or .Values.artifactory.fsGroupChangePolicy .Values.artifactory.seLinuxOptions }} + {{- fail "\n .Values.artifactory.fsGroupChangePolicy and .Values.artifactory.seLinuxOptions are no longer supported. You need to set these values at the pod security context level. Replace them with .Values.artifactory.podSecurityContext.fsGroupChangePolicy and .Values.artifactory.podSecurityContext.seLinuxOptions" }} +{{- end }} +{{- if .Values.initContainerImage }} + {{- fail "\n .Values.initContainerImage is no longer supported. Replace it with .Values.initContainers.image.registry .Values.initContainers.image.repository and .Values.initContainers.image.tag" }} +{{- end }} +{{- with .Values.artifactory.statefulset.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + serviceName: {{ template "artifactory-ha.primary.name" . }} + replicas: {{ .Values.artifactory.primary.replicaCount }} + updateStrategy: {{- toYaml .Values.artifactory.primary.updateStrategy | nindent 4}} + selector: + matchLabels: + app: {{ template "artifactory-ha.name" . }} + role: {{ template "artifactory-ha.primary.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + role: {{ template "artifactory-ha.primary.name" . }} + component: {{ .Values.artifactory.name }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + {{- with .Values.artifactory.primary.labels }} +{{ toYaml . | indent 8 }} + {{- end }} + annotations: + {{- if not .Values.artifactory.unifiedSecretInstallation }} + checksum/database-secrets: {{ include (print $.Template.BasePath "/artifactory-database-secrets.yaml") . | sha256sum }} + checksum/binarystore: {{ include (print $.Template.BasePath "/artifactory-binarystore-secret.yaml") . | sha256sum }} + checksum/systemyaml: {{ include (print $.Template.BasePath "/artifactory-system-yaml.yaml") . | sha256sum }} + {{- if .Values.access.accessConfig }} + checksum/access-config: {{ include (print $.Template.BasePath "/artifactory-access-config.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + checksum/gcpcredentials: {{ include (print $.Template.BasePath "/artifactory-gcp-credentials-secret.yaml") . | sha256sum }} + {{- end }} + {{- if not (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} + checksum/admin-creds: {{ include (print $.Template.BasePath "/admin-bootstrap-creds.yaml") . | sha256sum }} + {{- end }} + {{- else }} + checksum/artifactory-unified-secret: {{ include (print $.Template.BasePath "/artifactory-unified-secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.artifactory.annotations }} +{{ toYaml . | indent 8 }} + {{- end }} + spec: + {{- if .Values.artifactory.schedulerName }} + schedulerName: {{ .Values.artifactory.schedulerName | quote }} + {{- end }} + {{- if .Values.artifactory.priorityClass.existingPriorityClass }} + priorityClassName: {{ .Values.artifactory.priorityClass.existingPriorityClass }} + {{- else -}} + {{- if .Values.artifactory.priorityClass.create }} + priorityClassName: {{ default (include "artifactory-ha.fullname" .) .Values.artifactory.priorityClass.name }} + {{- end }} + {{- end }} + serviceAccountName: {{ template "artifactory-ha.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ add .Values.artifactory.terminationGracePeriodSeconds 10 }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory-ha.imagePullSecrets" . | indent 6 }} + {{- end }} + {{- if .Values.artifactory.podSecurityContext.enabled }} + securityContext: {{- omit .Values.artifactory.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.artifactory.topologySpreadConstraints }} + topologySpreadConstraints: +{{ tpl (toYaml .Values.artifactory.topologySpreadConstraints) . | indent 8 }} + {{- end }} + initContainers: + {{- if or .Values.artifactory.customInitContainersBegin .Values.global.customInitContainersBegin }} +{{ tpl (include "artifactory-ha.customInitContainersBegin" .) . | indent 6 }} + {{- end }} + {{- if .Values.artifactory.persistence.enabled }} + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + - name: "create-artifactory-data-dir" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > + mkdir -p {{ tpl .Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir . }}; + volumeMounts: + - mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + name: volume + {{- end }} + {{- end }} + {{- if .Values.artifactory.deleteDBPropertiesOnStartup }} + - name: "delete-db-properties" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - 'rm -fv {{ .Values.artifactory.persistence.mountPath }}/etc/db.properties' + volumeMounts: + - mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + name: volume + {{- end }} + {{- if or (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) .Values.artifactory.admin.password }} + - name: "access-bootstrap-creds" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > + echo "Preparing {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access; + cp -Lrf /tmp/access/bootstrap.creds {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds; + chmod 600 {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds; + volumeMounts: + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + {{- if or (not .Values.artifactory.unifiedSecretInstallation) (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} + - name: access-bootstrap-creds + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/access/bootstrap.creds" + {{- if and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey }} + subPath: {{ .Values.artifactory.admin.dataKey }} + {{- else }} + subPath: bootstrap.creds + {{- end }} + {{- end }} + {{- end }} + - name: 'copy-system-configurations' + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - '/bin/bash' + - '-c' + - > + if [[ -e "{{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml" ]]; then chmod 644 {{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml; fi; + echo "Copy system.yaml to {{ .Values.artifactory.persistence.mountPath }}/etc"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; + {{- if .Values.systemYamlOverride.existingSecret }} + cp -fv /tmp/etc/{{ .Values.systemYamlOverride.dataKey }} {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- else }} + cp -fv /tmp/etc/system.yaml {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- end }} + echo "Copy binarystore.xml file"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory; + cp -fv /tmp/etc/artifactory/binarystore.xml {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory/binarystore.xml; + {{- if .Values.access.accessConfig }} + echo "Copy access.config.patch.yml to {{ .Values.artifactory.persistence.mountPath }}/etc/access"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access; + cp -fv /tmp/etc/access.config.patch.yml {{ .Values.artifactory.persistence.mountPath }}/etc/access/access.config.patch.yml; + {{- end }} + {{- if .Values.access.resetAccessCAKeys }} + echo "Resetting Access CA Keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys; + touch {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/reset_ca_keys; + {{- end }} + {{- if .Values.access.customCertificatesSecretName }} + echo "Copying custom certificates to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys; + cp -fv /tmp/etc/tls.crt {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.crt; + cp -fv /tmp/etc/tls.key {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.private.key; + {{- end }} + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + echo "Copy joinKey to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security; + echo -n ${ARTIFACTORY_JOIN_KEY} > {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security/join.key; + {{- end }} + {{- if or .Values.artifactory.jfConnectToken .Values.artifactory.jfConnectTokenSecretName }} + echo "Copy jfConnectToken to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/registration_token"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/; + echo -n ${ARTIFACTORY_JFCONNECT_TOKEN} > {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/registration_token; + {{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + echo "Copy masterKey to {{ .Values.artifactory.persistence.mountPath }}/etc/security"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security; + echo -n ${ARTIFACTORY_MASTER_KEY} > {{ .Values.artifactory.persistence.mountPath }}/etc/security/master.key; + {{- end }} + env: + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + - name: ARTIFACTORY_JOIN_KEY + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + name: {{ include "artifactory-ha.joinKeySecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: join-key + {{- end }} + {{- if or .Values.artifactory.jfConnectToken .Values.artifactory.jfConnectTokenSecretName }} + - name: ARTIFACTORY_JFCONNECT_TOKEN + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.jfConnectTokenSecretName }} + name: {{ include "artifactory-ha.jfConnectTokenSecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: jfconnect-token + {{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + - name: ARTIFACTORY_MASTER_KEY + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + name: {{ include "artifactory-ha.masterKeySecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: master-key + {{- end }} + + ######################## Volume Mounts For copy-system-configurations ########################## + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + + ######################## SystemYaml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.systemYamlOverride.existingSecret }} + - name: systemyaml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + {{- if .Values.systemYamlOverride.existingSecret }} + mountPath: "/tmp/etc/{{.Values.systemYamlOverride.dataKey}}" + subPath: {{ .Values.systemYamlOverride.dataKey }} + {{- else }} + mountPath: "/tmp/etc/system.yaml" + subPath: system.yaml + {{- end }} + + ######################## Binarystore ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Access config ########################## + {{- if .Values.access.accessConfig }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + - name: access-config + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/access.config.patch.yml" + subPath: access.config.patch.yml + {{- end }} + + ######################## Access certs external secret ########################## + {{- if .Values.access.customCertificatesSecretName }} + - name: access-certs + mountPath: "/tmp/etc/tls.crt" + subPath: tls.crt + - name: access-certs + mountPath: "/tmp/etc/tls.key" + subPath: tls.key + {{- end }} + + {{- if or .Values.artifactory.customCertificates.enabled .Values.global.customCertificates.enabled }} + - name: copy-custom-certificates + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory-ha.copyCustomCerts" . | indent 10 }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: ca-certs + mountPath: "/tmp/certs" + {{- end }} + + {{- if .Values.artifactory.circleOfTrustCertificatesSecret }} + - name: copy-circle-of-trust-certificates + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory.copyCircleOfTrustCertsCerts" . | indent 10 }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: circle-of-trust-certs + mountPath: "/tmp/circleoftrustcerts" + {{- end }} + + {{- if .Values.waitForDatabase }} + {{- if or .Values.postgresql.enabled }} + - name: "wait-for-db" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - /bin/bash + - -c + - | + echo "Waiting for postgresql to come up" + ready=false; + while ! $ready; do echo waiting; + timeout 2s bash -c " + {{- if .Values.artifactory.migration.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.artifactory.migration.preStartCommand . }}; + {{- end }} + scriptsPath="/opt/jfrog/artifactory/app/bin"; + mkdir -p $scriptsPath; + echo "Copy migration scripts and Run migration"; + cp -fv /tmp/migrate.sh $scriptsPath/migrate.sh; + cp -fv /tmp/migrationHelmInfo.yaml $scriptsPath/migrationHelmInfo.yaml; + cp -fv /tmp/migrationStatus.sh $scriptsPath/migrationStatus.sh; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/log; + bash $scriptsPath/migrationStatus.sh {{ include "artifactory-ha.app.version" . }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; + env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} + - name: JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: migration-scripts + mountPath: "/tmp/migrate.sh" + subPath: migrate.sh + - name: migration-scripts + mountPath: "/tmp/migrationHelmInfo.yaml" + subPath: migrationHelmInfo.yaml + - name: migration-scripts + mountPath: "/tmp/migrationStatus.sh" + subPath: migrationStatus.sh + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + + ######################## Artifactory persistence fs ########################## + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + + ######################## CustomVolumeMounts ########################## + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + {{- end }} + +{{- end }} + + {{- if .Values.hostAliases }} + hostAliases: +{{ toYaml .Values.hostAliases | indent 6 }} + {{- end }} + containers: + {{- if .Values.splitServicesToContainers }} + - name: {{ .Values.router.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "router") }} + imagePullPolicy: {{ .Values.router.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/router/app/bin/entrypoint-router.sh; + {{- with .Values.router.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_ROUTER_TOPOLOGY_LOCAL_REQUIREDSERVICETYPES + value: {{ include "artifactory-ha.router.requiredServiceTypes" . }} +{{- with .Values.router.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - name: http + containerPort: {{ .Values.router.internalPort }} + volumeMounts: + - name: volume + mountPath: {{ .Values.router.persistence.mountPath | quote }} +{{- with .Values.router.customVolumeMounts }} +{{ tpl . $ | indent 8 }} +{{- end }} + resources: +{{ toYaml .Values.router.resources | indent 10 }} + {{- if .Values.router.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.router.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.router.readinessProbe.enabled }} + readinessProbe: +{{ tpl .Values.router.readinessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.router.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.router.livenessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.enabled }} + - name: {{ .Values.frontend.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/third-party/node/bin/node /opt/jfrog/artifactory/app/frontend/bin/server/dist/bundle.js /opt/jfrog/artifactory/app/frontend + {{- with .Values.frontend.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + - name : JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.frontend.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.frontend.resources | indent 10 }} + {{- if .Values.frontend.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.frontend.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.frontend.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.evidence.enabled }} + - name: {{ .Values.evidence.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/evidence/bin/jf-evidence start + {{- with .Values.evidence.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.evidence.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.evidence.internalPort }} + name: http-evidence + - containerPort: {{ .Values.evidence.externalPort }} + name: grpc-evidence + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.evidence.resources | indent 10 }} + {{- if .Values.evidence.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.evidence.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.evidence.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.evidence.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.metadata.enabled }} + - name: {{ .Values.metadata.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "metadata") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/metadata/bin/jf-metadata start + {{- with .Values.metadata.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.metadata.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.metadata.resources | indent 10 }} + {{- if .Values.metadata.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.metadata.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.metadata.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.metadata.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.event.enabled }} + - name: {{ .Values.event.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/event/bin/jf-event start + {{- with .Values.event.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.event.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.event.resources | indent 10 }} + {{- if .Values.event.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.event.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.event.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.event.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.jfconnect.enabled }} + - name: {{ .Values.jfconnect.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/jfconnect/bin/jf-connect start + {{- with .Values.jfconnect.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.jfconnect.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.jfconnect.resources | indent 10 }} + {{- if .Values.jfconnect.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.jfconnect.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.jfconnect.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.jfconnect.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.federation.enabled .Values.federation.embedded }} + - name: {{ .Values.federation.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/third-party/java/bin/java {{ .Values.federation.extraJavaOpts }} -jar /opt/jfrog/artifactory/app/rtfs/lib/jf-rtfs + {{- with .Values.federation.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + # TODO - Password,Url,Username - should be derived from env variable +{{- with .Values.federation.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.federation.resources | indent 10 }} + {{- if .Values.federation.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.federation.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.federation.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.federation.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.observability.enabled }} + - name: {{ .Values.observability.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "observability") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/observability/bin/jf-observability start + {{- with .Values.observability.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.observability.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.observability.resources | indent 10 }} + {{- if .Values.observability.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.observability.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.observability.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.observability.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.access.enabled (not (.Values.access.runOnArtifactoryTomcat | default false)) }} + - name: {{ .Values.access.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.access.resources }} + resources: +{{ toYaml .Values.access.resources | indent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + set -e; + {{- if .Values.access.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.access.preStartCommand . }}; + {{- end }} + exec /opt/jfrog/artifactory/app/access/bin/entrypoint-access.sh + {{- with .Values.access.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.access.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentPodVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: awsmp-product-license + mountPath: "/var/run/secrets/product-license" + {{- end }} + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + + ######################## Artifactory persistence fs ########################## + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + + + ######################## Artifactory license ########################## + {{- if or .Values.artifactory.license.secret .Values.artifactory.license.licenseKey }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.license.secret }} + - name: artifactory-license + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/artifactory.cluster.license" + {{- if .Values.artifactory.license.secret }} + subPath: {{ .Values.artifactory.license.dataKey }} + {{- else if .Values.artifactory.license.licenseKey }} + subPath: artifactory.lic + {{- end }} + {{- end }} + {{- end }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + {{- if .Values.access.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.access.startupProbe.config . | indent 10 }} + {{- end }} + {{- if semverCompare " + set -e; + if [ -d /artifactory_extra_conf ] && [ -d /artifactory_bootstrap ]; then + echo "Copying bootstrap config from /artifactory_extra_conf to /artifactory_bootstrap"; + cp -Lrfv /artifactory_extra_conf/ /artifactory_bootstrap/; + fi; + {{- if .Values.artifactory.configMapName }} + echo "Copying bootstrap configs"; + cp -Lrf /bootstrap/* /artifactory_bootstrap/; + {{- end }} + {{- if .Values.artifactory.userPluginSecrets }} + echo "Copying plugins"; + cp -Lrf /tmp/plugin/*/* /artifactory_bootstrap/plugins; + {{- end }} + {{- range .Values.artifactory.copyOnEveryStartup }} + {{- $targetPath := printf "%s/%s" $.Values.artifactory.persistence.mountPath .target }} + {{- $baseDirectory := regexFind ".*/" $targetPath }} + mkdir -p {{ $baseDirectory }}; + cp -Lrf {{ .source }} {{ $.Values.artifactory.persistence.mountPath }}/{{ .target }}; + {{- end }} + {{- with .Values.artifactory.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl . $ }}; + {{- end }} + {{- with .Values.artifactory.primary.preStartCommand }} + echo "Running primary specific custom preStartCommand command"; + {{ tpl . $ }}; + {{- end }} + exec /entrypoint-artifactory.sh + {{- with .Values.artifactory.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if .Values.aws.license.enabled }} + - name: IS_AWS_LICENSE + value: "true" + - name: AWS_REGION + value: {{ .Values.aws.region | quote }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE + value: "/var/run/secrets/product-license/license_token" + - name: AWS_ROLE_ARN + valueFrom: + secretKeyRef: + name: {{ .Values.aws.licenseConfigSecretName }} + key: iam_role + {{- end }} + {{- end }} + {{- if .Values.splitServicesToContainers }} + - name : JF_ROUTER_ENABLED + value: "true" + - name : JF_ROUTER_SERVICE_ENABLED + value: "false" + - name : JF_EVENT_ENABLED + value: "false" + - name : JF_METADATA_ENABLED + value: "false" + - name : JF_FRONTEND_ENABLED + value: "false" + - name: JF_FEDERATION_ENABLED + value: "false" + - name : JF_OBSERVABILITY_ENABLED + value: "false" + - name : JF_JFCONNECT_SERVICE_ENABLED + value: "false" + - name : JF_EVIDENCE_ENABLED + value: "false" + {{- if not (.Values.access.runOnArtifactoryTomcat | default false) }} + - name : JF_ACCESS_ENABLED + value: "false" + {{- end}} + {{- end }} + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory-ha.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} + - name: JF_SHARED_NODE_HAENABLED + value: "true" +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.artifactory.internalPort }} + name: http + - containerPort: {{ .Values.artifactory.internalArtifactoryPort }} + name: http-internal + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- if .Values.artifactory.primary.javaOpts.jmx.enabled }} + - containerPort: {{ .Values.artifactory.primary.javaOpts.jmx.port }} + name: tcp-jmx + {{- end }} + {{- if .Values.artifactory.ssh.enabled }} + - containerPort: {{ .Values.artifactory.ssh.internalPort }} + name: tcp-ssh + {{- end }} + + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.artifactory.customPersistentPodVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentPodVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentPodVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: awsmp-product-license + mountPath: "/var/run/secrets/product-license" + {{- end }} + {{- if .Values.artifactory.userPluginSecrets }} + - name: bootstrap-plugins + mountPath: "/artifactory_bootstrap/plugins/" + {{- range .Values.artifactory.userPluginSecrets }} + - name: {{ tpl . $ }} + mountPath: "/tmp/plugin/{{ tpl . $ }}" + {{- end }} + {{- end }} + - name: volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + + ######################## Artifactory persistence fs ########################## + {{- if eq .Values.artifactory.persistence.type "file-system" }} + {{- if .Values.artifactory.persistence.fileSystem.existingSharedClaim.enabled }} + {{- range $sharedClaimNumber, $e := until (.Values.artifactory.persistence.fileSystem.existingSharedClaim.numberOfExistingClaims|int) }} + - name: artifactory-ha-data-{{ $sharedClaimNumber }} + mountPath: "{{ tpl $.Values.artifactory.persistence.fileSystem.existingSharedClaim.dataDir $ }}/filestore{{ $sharedClaimNumber }}" + {{- end }} + - name: artifactory-ha-backup + mountPath: "{{ $.Values.artifactory.persistence.fileSystem.existingSharedClaim.backupDir }}" + {{- end }} + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-ha-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-ha-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystoreXml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence googleStorage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + {{- end }} + + ######################## Artifactory configMapName ########################## + {{- if .Values.artifactory.configMapName }} + - name: bootstrap-config + mountPath: "/bootstrap/" + {{- end }} + + ######################## Artifactory license ########################## + {{- if or .Values.artifactory.license.secret .Values.artifactory.license.licenseKey }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.license.secret }} + - name: artifactory-license + {{- else }} + - name: {{ include "artifactory-ha.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/artifactory.cluster.license" + {{- if .Values.artifactory.license.secret }} + subPath: {{ .Values.artifactory.license.dataKey }} + {{- else if .Values.artifactory.license.licenseKey }} + subPath: artifactory.lic + {{- end }} + {{- end }} + + - name: installer-info + mountPath: "/artifactory_bootstrap/info/installer-info.json" + subPath: installer-info.json + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory-ha.customVolumeMounts" .) . | indent 8 }} + {{- end }} + resources: +{{ toYaml .Values.artifactory.primary.resources | indent 10 }} + {{- if .Values.artifactory.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.artifactory.startupProbe.config . | indent 10 }} + {{- end }} + {{- if and (not .Values.splitServicesToContainers) (semverCompare "=1.18.0-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.defaultBackend.enabled }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + defaultBackend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- else }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} + {{- end }} + rules: +{{- if .Values.ingress.hosts }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingress.routerPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- if not $.Values.ingress.disableRouterBypass }} + - path: {{ $.Values.ingress.artifactoryPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $artifactoryServicePort }} + {{- end }} + {{- if and $.Values.federation.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" $.Values.artifactory.image.repository)) }} + - path: {{ $.Values.ingress.rtfsPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $.Values.federation.internalPort }} + {{- end }} + {{- end }} + {{- else }} + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingress.routerPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + - path: {{ $.Values.ingress.artifactoryPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $artifactoryServicePort }} + {{- end }} + {{- end }} +{{- end -}} + {{- with .Values.ingress.additionalRules }} +{{ tpl . $ | indent 2 }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} + +{{- if .Values.customIngress }} +--- +{{ .Values.customIngress | toYaml | trimSuffix "\n" }} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/logger-configmap.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/logger-configmap.yaml new file mode 100644 index 0000000000..d3597905d9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/logger-configmap.yaml @@ -0,0 +1,63 @@ +{{- if or .Values.artifactory.loggers .Values.artifactory.catalinaLoggers }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory-ha.fullname" . }}-logger + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + tail-log.sh: | + #!/bin/sh + + LOG_DIR=$1 + LOG_NAME=$2 + PID= + + # Wait for log dir to appear + while [ ! -d ${LOG_DIR} ]; do + sleep 1 + done + + cd ${LOG_DIR} + + LOG_PREFIX=$(echo ${LOG_NAME} | sed 's/.log$//g') + + # Find the log to tail + LOG_FILE=$(ls -1t ./${LOG_PREFIX}.log 2>/dev/null) + + # Wait for the log file + while [ -z "${LOG_FILE}" ]; do + sleep 1 + LOG_FILE=$(ls -1t ./${LOG_PREFIX}.log 2>/dev/null) + done + + echo "Log file ${LOG_FILE} is ready!" + + # Get inode number + INODE_ID=$(ls -i ${LOG_FILE}) + + # echo "Tailing ${LOG_FILE}" + tail -F ${LOG_FILE} & + PID=$! + + # Loop forever to see if a new log was created + while true; do + # Check inode number + NEW_INODE_ID=$(ls -i ${LOG_FILE}) + + # If inode number changed, this means log was rotated and need to start a new tail + if [ "${INODE_ID}" != "${NEW_INODE_ID}" ]; then + kill -9 ${PID} 2>/dev/null + INODE_ID="${NEW_INODE_ID}" + + # Start a new tail + tail -F ${LOG_FILE} & + PID=$! + fi + sleep 1 + done + +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-artifactory-conf.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-artifactory-conf.yaml new file mode 100644 index 0000000000..97ae5f27be --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-artifactory-conf.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.nginx.customArtifactoryConfigMap) .Values.nginx.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory-ha.fullname" . }}-nginx-artifactory-conf + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + artifactory.conf: | +{{- if .Values.nginx.artifactoryConf }} +{{ tpl .Values.nginx.artifactoryConf . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/nginx-artifactory-conf.yaml" ) . | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-certificate-secret.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-certificate-secret.yaml new file mode 100644 index 0000000000..29c77ad5a4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-certificate-secret.yaml @@ -0,0 +1,14 @@ +{{- if and (not .Values.nginx.tlsSecretName) .Values.nginx.enabled .Values.nginx.https.enabled }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: {{ template "artifactory-ha.fullname" . }}-nginx-certificate + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: +{{ ( include "artifactory-ha.gen-certs" . ) | indent 2 }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-conf.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-conf.yaml new file mode 100644 index 0000000000..4f0d65f25c --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-conf.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.nginx.customConfigMap) .Values.nginx.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory-ha.fullname" . }}-nginx-conf + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + nginx.conf: | +{{- if .Values.nginx.mainConf }} +{{ tpl .Values.nginx.mainConf . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/nginx-main-conf.yaml" ) . | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-deployment.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-deployment.yaml new file mode 100644 index 0000000000..d43689b8cd --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-deployment.yaml @@ -0,0 +1,221 @@ +{{- if .Values.nginx.enabled -}} +{{- $serviceName := include "artifactory-ha.fullname" . -}} +{{- $servicePort := .Values.artifactory.externalPort -}} +apiVersion: apps/v1 +kind: {{ .Values.nginx.kind }} +metadata: + name: {{ template "artifactory-ha.nginx.fullname" . }} + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: {{ .Values.nginx.name }} +{{- if .Values.nginx.labels }} +{{ toYaml .Values.nginx.labels | indent 4 }} +{{- end }} +{{- with .Values.nginx.deployment.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if ne .Values.nginx.kind "DaemonSet" }} + replicas: {{ .Values.nginx.replicaCount }} +{{- end }} + selector: + matchLabels: + app: {{ template "artifactory-ha.name" . }} + release: {{ .Release.Name }} + component: {{ .Values.nginx.name }} + template: + metadata: + annotations: + checksum/nginx-conf: {{ include (print $.Template.BasePath "/nginx-conf.yaml") . | sha256sum }} + checksum/nginx-artifactory-conf: {{ include (print $.Template.BasePath "/nginx-artifactory-conf.yaml") . | sha256sum }} + {{- range $key, $value := .Values.nginx.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + component: {{ .Values.nginx.name }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- if .Values.nginx.labels }} +{{ toYaml .Values.nginx.labels | indent 8 }} +{{- end }} + spec: + {{- if .Values.nginx.podSecurityContext.enabled }} + securityContext: {{- omit .Values.nginx.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "artifactory-ha.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ .Values.nginx.terminationGracePeriodSeconds }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory-ha.imagePullSecrets" . | indent 6 }} + {{- end }} + {{- if .Values.nginx.priorityClassName }} + priorityClassName: {{ .Values.nginx.priorityClassName | quote }} + {{- end }} + {{- if .Values.nginx.topologySpreadConstraints }} + topologySpreadConstraints: +{{ tpl (toYaml .Values.nginx.topologySpreadConstraints) . | indent 8 }} + {{- end }} + initContainers: + {{- if .Values.nginx.customInitContainers }} +{{ tpl (include "artifactory.nginx.customInitContainers" .) . | indent 6 }} + {{- end }} + - name: "setup" + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.imagePullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/sh' + - '-c' + - > + rm -rfv {{ .Values.nginx.persistence.mountPath }}/lost+found; + mkdir -p {{ .Values.nginx.persistence.mountPath }}/logs; + resources: + {{- toYaml .Values.initContainers.resources | nindent 10 }} + volumeMounts: + - mountPath: {{ .Values.nginx.persistence.mountPath | quote }} + name: nginx-volume + containers: + - name: {{ .Values.nginx.name }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list . "nginx") }} + imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} + {{- if .Values.nginx.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.nginx.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.nginx.customCommand }} + command: +{{- tpl (include "nginx.command" .) . | indent 10 }} + {{- end }} + ports: +{{ if .Values.nginx.customPorts }} +{{ toYaml .Values.nginx.customPorts | indent 8 }} +{{ end }} + # DEPRECATION NOTE: The following is to maintain support for values pre 1.3.1 and + # will be cleaned up in a later version + {{- if .Values.nginx.http }} + {{- if .Values.nginx.http.enabled }} + - containerPort: {{ .Values.nginx.http.internalPort }} + name: http + {{- end }} + {{- else }} # DEPRECATED + - containerPort: {{ .Values.nginx.internalPortHttp }} + name: http-internal + {{- end }} + {{- if .Values.nginx.https }} + {{- if .Values.nginx.https.enabled }} + - containerPort: {{ .Values.nginx.https.internalPort }} + name: https + {{- end }} + {{- else }} # DEPRECATED + - containerPort: {{ .Values.nginx.internalPortHttps }} + name: https-internal + {{- end }} + {{- if .Values.artifactory.ssh.enabled }} + - containerPort: {{ .Values.nginx.ssh.internalPort }} + name: tcp-ssh + {{- end }} + {{- with .Values.nginx.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-artifactory-conf + mountPath: "{{ .Values.nginx.persistence.mountPath }}/conf.d/" + - name: nginx-volume + mountPath: {{ .Values.nginx.persistence.mountPath | quote }} + {{- if .Values.nginx.https.enabled }} + - name: ssl-certificates + mountPath: "{{ .Values.nginx.persistence.mountPath }}/ssl" + {{- end }} + {{- if .Values.nginx.customVolumeMounts }} +{{ tpl (include "artifactory.nginx.customVolumeMounts" .) . | indent 8 }} + {{- end }} + resources: +{{ toYaml .Values.nginx.resources | indent 10 }} + {{- if .Values.nginx.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.nginx.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.nginx.readinessProbe.enabled }} + readinessProbe: +{{ tpl .Values.nginx.readinessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.nginx.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.nginx.livenessProbe.config . | indent 10 }} + {{- end }} + {{- $mountPath := .Values.nginx.persistence.mountPath }} + {{- range .Values.nginx.loggers }} + - name: {{ . | replace "_" "-" | replace "." "-" }} + image: {{ include "artifactory-ha.getImageInfoByValue" (list $ "initContainers") }} + imagePullPolicy: {{ $.Values.initContainers.image.pullPolicy }} + command: + - tail + args: + - '-F' + - '{{ $mountPath }}/logs/{{ . }}' + volumeMounts: + - name: nginx-volume + mountPath: {{ $mountPath }} + resources: +{{ toYaml $.Values.nginx.loggersResources | indent 10 }} + {{- end }} + {{- if .Values.nginx.customSidecarContainers }} +{{ tpl (include "artifactory.nginx.customSidecarContainers" .) . | indent 6 }} + {{- end }} + {{- if or .Values.nginx.nodeSelector .Values.global.nodeSelector }} +{{ tpl (include "nginx.nodeSelector" .) . | indent 6 }} + {{- end }} + {{- with .Values.nginx.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + {{- if .Values.nginx.customVolumes }} +{{ tpl (include "artifactory.nginx.customVolumes" .) . | indent 6 }} + {{- end }} + - name: nginx-conf + configMap: + {{- if .Values.nginx.customConfigMap }} + name: {{ .Values.nginx.customConfigMap }} + {{- else }} + name: {{ template "artifactory-ha.fullname" . }}-nginx-conf + {{- end }} + - name: nginx-artifactory-conf + configMap: + {{- if .Values.nginx.customArtifactoryConfigMap }} + name: {{ .Values.nginx.customArtifactoryConfigMap }} + {{- else }} + name: {{ template "artifactory-ha.fullname" . }}-nginx-artifactory-conf + {{- end }} + + - name: nginx-volume + {{- if .Values.nginx.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.nginx.persistence.existingClaim | default (include "artifactory-ha.nginx.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.nginx.https.enabled }} + - name: ssl-certificates + secret: + {{- if .Values.nginx.tlsSecretName }} + secretName: {{ .Values.nginx.tlsSecretName }} + {{- else }} + secretName: {{ template "artifactory-ha.fullname" . }}-nginx-certificate + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-pdb.yaml b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-pdb.yaml new file mode 100644 index 0000000000..0aed993682 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.90.9/templates/nginx-pdb.yaml @@ -0,0 +1,23 @@ +{{- if .Values.nginx.enabled -}} +{{- if semverCompare " --from-literal=license_token=${TOKEN} --from-literal=iam_role=${ROLE_ARN}` +aws: + license: + enabled: false + licenseConfigSecretName: + region: us-east-1 +## Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled Enabled containers' Security Context +## @param containerSecurityContext.runAsNonRoot Set container's Security Context runAsNonRoot +## @param containerSecurityContext.privileged Set container's Security Context privileged +## @param containerSecurityContext.allowPrivilegeEscalation Set container's Security Context allowPrivilegeEscalation +## @param containerSecurityContext.capabilities.drop List of capabilities to be dropped +## @param containerSecurityContext.seccompProfile.type Set container's Security Context seccomp profile +## +containerSecurityContext: + enabled: true + runAsNonRoot: true + privileged: false + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL +## The following router settings are to configure only when splitServicesToContainers set to true +router: + name: router + image: + registry: releases-docker.jfrog.io + repository: jfrog/router + tag: 7.118.0 + pullPolicy: IfNotPresent + serviceRegistry: + ## Service registry (Access) TLS verification skipped if enabled + insecure: false + internalPort: 8082 + externalPort: 8082 + tlsEnabled: false + ## Extra environment variables that can be used to tune router to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: MY_ENV_VAR + # value: "" + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "1Gi" + # cpu: "1" + + ## Add lifecycle hooks for router container + lifecycle: + ## From Artifactory versions 7.52.x, Wait for Artifactory to complete any open uploads or downloads before terminating + preStop: + exec: + command: ["sh", "-c", "while [[ $(curl --fail --silent --connect-timeout 2 http://localhost:8081/artifactory/api/v1/system/liveness) =~ OK ]]; do echo Artifactory is still alive; sleep 2; done"] + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + ## Add custom volumesMounts + customVolumeMounts: | + # - name: custom-script + # mountPath: /scripts/script.sh + # subPath: script.sh + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} {{ include "artifactory-ha.scheme" . }}://localhost:{{ .Values.router.internalPort }}/router/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " prepended. + unifiedSecretPrependReleaseName: true + image: + registry: releases-docker.jfrog.io + repository: jfrog/artifactory-pro + # tag: + pullPolicy: IfNotPresent + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + schedulerName: + ## Create a priority class for the Artifactory pods or use an existing one + ## NOTE - Maximum allowed value of a user defined priority is 1000000000 + priorityClass: + create: false + value: 1000000000 + ## Override default name + # name: + ## Use an existing priority class + # existingPriorityClass: + ## Delete the db.properties file in ARTIFACTORY_HOME/etc/db.properties + deleteDBPropertiesOnStartup: true + database: + maxOpenConnections: 80 + tomcat: + maintenanceConnector: + port: 8091 + connector: + maxThreads: 200 + sendReasonPhrase: false + extraConfig: 'acceptCount="400"' + ## certificates added to this secret will be copied to $JFROG_HOME/artifactory/var/etc/security/keys/trusted directory + customCertificates: + enabled: false + # certificateSecretName: + ## Support for metrics is only available for Artifactory 7.7.x (appVersions) and above. + ## To enable set `.Values.artifactory.metrics.enabled` to `true` + ## Note: Depricated `openMetrics` as part of 7.87.x and renamed to `metrics` + ## Refer - https://www.jfrog.com/confluence/display/JFROG/Open+Metrics + metrics: + enabled: false + ## Settings for pushing metrics to Insight - enable filebeat to true + filebeat: + enabled: false + log: + enabled: false + ## Log level for filebeat. Possible values: debug, info, warning, or error. + level: "info" + ## Elasticsearch details for filebeat to connect + elasticsearch: + url: "Elasticsearch url where JFrog Insight is installed For example, http://:8082" + username: "" + password: "" + ## Support for Cold Artifact Storage + ## set 'coldStorage.enabled' to 'true' only for Artifactory instance that you are designating as the Cold instance + ## Refer - https://jfrog.com/help/r/jfrog-platform-administration-documentation/setting-up-cold-artifact-storage + coldStorage: + enabled: false + ## This directory is intended for use with NFS eventual configuration for HA + ## When enabling this section, The system.yaml will include haDataDir section. + ## The location of Artifactory Data directory and Artifactory Filestore will be modified accordingly and will be shared among all nodes. + ## It's recommended to leave haDataDir disabled, and the default BinarystoreXml will set the Filestore location as configured in artifactory.persistence.nfs.dataDir. + haDataDir: + enabled: false + path: + haBackupDir: + enabled: false + path: + ## Files to copy to ARTIFACTORY_HOME/ on each Artifactory startup + ## Note : From 107.46.x chart versions, copyOnEveryStartup is not needed for binarystore.xml, it is always copied via initContainers + copyOnEveryStartup: + ## Absolute path + # - source: /artifactory_bootstrap/artifactory.cluster.license + ## Relative to ARTIFACTORY_HOME/ + # target: etc/artifactory/ + + ## Sidecar containers for tailing Artifactory logs + loggers: [] + # - access-audit.log + # - access-request.log + # - access-security-audit.log + # - access-service.log + # - artifactory-access.log + # - artifactory-event.log + # - artifactory-import-export.log + # - artifactory-request.log + # - artifactory-service.log + # - frontend-request.log + # - frontend-service.log + # - metadata-request.log + # - metadata-service.log + # - router-request.log + # - router-service.log + # - router-traefik.log + # - derby.log + + ## Loggers containers resources + loggersResources: {} + # requests: + # memory: "10Mi" + # cpu: "10m" + # limits: + # memory: "100Mi" + # cpu: "50m" + + ## Sidecar containers for tailing Tomcat (catalina) logs + catalinaLoggers: [] + # - tomcat-catalina.log + # - tomcat-localhost.log + + ## Tomcat (catalina) loggers resources + catalinaLoggersResources: {} + # requests: + # memory: "10Mi" + # cpu: "10m" + # limits: + # memory: "100Mi" + # cpu: "50m" + + ## Migration support from 6.x to 7.x. + migration: + enabled: false + timeoutSeconds: 3600 + ## Extra pre-start command in migration Init Container to install JDBC driver for MySql/MariaDb/Oracle + # preStartCommand: "mkdir -p /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib; cd /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib && curl -o /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib/mysql-connector-java-5.1.41.jar https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.41/mysql-connector-java-5.1.41.jar" + ## Add custom init containers execution before predefined init containers + customInitContainersBegin: | + # - name: "custom-setup" + # image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'touch {{ .Values.artifactory.persistence.mountPath }}/example-custom-setup' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: volume + ## Add custom init containers + ## Add custom init containers execution after predefined init containers + customInitContainers: | + # - name: "custom-systemyaml-setup" + # image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'curl -o {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml https:///systemyaml' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: volume + ## Add custom sidecar containers + ## - The provided example uses a custom volume (customVolumes) + ## - The provided example shows running container as root (id 0) + customSidecarContainers: | + # - name: "sidecar-list-etc" + # image: {{ include "artifactory-ha.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'sh /scripts/script.sh' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: volume + # - mountPath: "/scripts/script.sh" + # name: custom-script + # subPath: script.sh + # resources: + # requests: + # memory: "32Mi" + # cpu: "50m" + # limits: + # memory: "128Mi" + # cpu: "100m" + ## Add custom volumes + ## If .Values.artifactory.unifiedSecretInstallation is true then secret name should be '{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret'. + customVolumes: | + # - name: custom-script + # configMap: + # name: custom-script + ## Add custom volumesMounts + customVolumeMounts: | + # - name: custom-script + # mountPath: "/scripts/script.sh" + # subPath: script.sh + # - name: posthook-start + # mountPath: "/scripts/posthoook-start.sh" + # subPath: posthoook-start.sh + # - name: prehook-start + # mountPath: "/scripts/prehook-start.sh" + # subPath: prehook-start.sh + ## Add custom persistent volume mounts - Available to the entire namespace + customPersistentVolumeClaim: {} + # name: + # mountPath: + # accessModes: + # - "-" + # size: + # storageClassName: + + ## Artifactory HA requires a unique master key. Each Artifactory node must have the same master key! + ## You can generate one with the command: "openssl rand -hex 32" + ## Pass it to helm with '--set artifactory.masterKey=${MASTER_KEY}' + ## Alternatively, you can use a pre-existing secret with a key called master-key by specifying masterKeySecretName + ## IMPORTANT: You should NOT use the example masterKey for a production deployment! + ## IMPORTANT: This is a mandatory for fresh Install of 7.x (App version) + # masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + # masterKeySecretName: + + ## Join Key to connect to other services to Artifactory. + ## IMPORTANT: Setting this value overrides the existing joinKey + ## IMPORTANT: You should NOT use the example joinKey for a production deployment! + # joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + ## Alternatively, you can use a pre-existing secret with a key called join-key by specifying joinKeySecretName + # joinKeySecretName: + + ## Registration Token for JFConnect + # jfConnectToken: + ## Alternatively, you can use a pre-existing secret with a key called jfconnect-token by specifying jfConnectTokenSecretName + # jfConnectTokenSecretName: + + ## Add custom secrets - secret per file + ## If .Values.artifactory.unifiedSecretInstallation is true then secret name should be '{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret' common to all secrets + customSecrets: + # - name: custom-secret + # key: custom-secret.yaml + # data: > + # custom_secret_config: + # parameter1: value1 + # parameter2: value2 + # - name: custom-secret2 + # key: custom-secret2.config + # data: | + # here the custom secret 2 config + + ## If false, all service console logs will not redirect to a common console.log + consoleLog: false + ## admin allows to set the password for the default admin user. + ## See: https://www.jfrog.com/confluence/display/JFROG/Users+and+Groups#UsersandGroups-RecreatingtheDefaultAdminUserrecreate + admin: + ip: "127.0.0.1" + username: "admin" + password: + secret: + dataKey: + ## Artifactory license. + license: + ## licenseKey is the license key in plain text. Use either this or the license.secret setting + licenseKey: + ## If artifactory.license.secret is passed, it will be mounted as + ## ARTIFACTORY_HOME/etc/artifactory.cluster.license and loaded at run time. + secret: + ## The dataKey should be the name of the secret data key created. + dataKey: + ## Create configMap with artifactory.config.import.xml and security.import.xml and pass name of configMap in following parameter + configMapName: + ## Add any list of configmaps to Artifactory + configMaps: | + # posthook-start.sh: |- + # echo "This is a post start script" + # posthook-end.sh: |- + # echo "This is a post end script" + ## List of secrets for Artifactory user plugins. + ## One Secret per plugin's files. + userPluginSecrets: + # - archive-old-artifacts + # - build-cleanup + # - webhook + # - '{{ template "my-chart.fullname" . }}' + + ## Extra pre-start command to install JDBC driver for MySql/MariaDb/Oracle + # preStartCommand: "mkdir -p /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib; cd /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib && curl -o /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib/mysql-connector-java-5.1.41.jar https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.41/mysql-connector-java-5.1.41.jar" + + ## Add lifecycle hooks for artifactory container + lifecycle: {} + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + # preStop: + # exec: + # command: ["/bin/sh","-c","echo Hello from the preStop handler"] + + ## Extra environment variables that can be used to tune Artifactory to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: SERVER_XML_ARTIFACTORY_PORT + # value: "8081" + # - name: SERVER_XML_ARTIFACTORY_MAX_THREADS + # value: "200" + # - name: SERVER_XML_ACCESS_MAX_THREADS + # value: "50" + # - name: SERVER_XML_ARTIFACTORY_EXTRA_CONFIG + # value: "" + # - name: SERVER_XML_ACCESS_EXTRA_CONFIG + # value: "" + # - name: SERVER_XML_EXTRA_CONNECTOR + # value: "" + # - name: DB_POOL_MAX_ACTIVE + # value: "100" + # - name: DB_POOL_MAX_IDLE + # value: "10" + # - name: MY_SECRET_ENV_VAR + # valueFrom: + # secretKeyRef: + # name: my-secret-name + # key: my-secret-key + + ## System YAML entries now reside under files/system.yaml. + ## You can provide the specific values that you want to add or override under 'artifactory.extraSystemYaml'. + ## For example: + ## extraSystemYaml: + ## shared: + ## node: + ## id: my-instance + ## The entries provided under 'artifactory.extraSystemYaml' are merged with files/system.yaml to create the final system.yaml. + ## If you have already provided system.yaml under, 'artifactory.systemYaml', the values in that entry take precedence over files/system.yaml + ## You can modify specific entries with your own value under `artifactory.extraSystemYaml`, The values under extraSystemYaml overrides the values under 'artifactory.systemYaml' and files/system.yaml + extraSystemYaml: {} + ## systemYaml is intentionally commented and the previous content has been moved under files/system.yaml. + ## You have to add the all entries of the system.yaml file here, and it overrides the values in files/system.yaml. + # systemYaml: + + ## IMPORTANT: If overriding artifactory.internalPort: + ## DO NOT use port lower than 1024 as Artifactory runs as non-root and cannot bind to ports lower than 1024! + externalPort: 8082 + internalPort: 8082 + externalArtifactoryPort: 8081 + internalArtifactoryPort: 8081 + terminationGracePeriodSeconds: 30 + ## Pod Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## @param artifactory.podSecurityContext.enabled Enable security context + ## @param artifactory.podSecurityContext.runAsNonRoot Set pod's Security Context runAsNonRoot + ## @param artifactory.podSecurityContext.runAsUser User ID for the pod + ## @param artifactory.podSecurityContext.runASGroup Group ID for the pod + ## @param artifactory.podSecurityContext.fsGroup Group ID for the pod + ## + podSecurityContext: + enabled: true + runAsNonRoot: true + runAsUser: 1030 + runAsGroup: 1030 + fsGroup: 1030 + # fsGroupChangePolicy: "Always" + # seLinuxOptions: {} + ## The following settings are to configure the frequency of the liveness and startup probes. + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:{{ .Values.artifactory.tomcat.maintenanceConnector.port }}/artifactory/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClassName: "-" + + ## Set the persistence storage type. This will apply the matching binarystore.xml to Artifactory config + ## Supported types are: + ## file-system (default) + ## nfs + ## google-storage + ## google-storage-v2 + ## google-storage-v2-direct (Recommended for GCS - Google Cloud Storage) + ## aws-s3-v3 + ## s3-storage-v3-direct (Recommended for AWS S3) + ## s3-storage-v3-archive + ## azure-blob + ## azure-blob-storage-direct + ## azure-blob-storage-v2-direct (Recommended for Azure Blob Storage) + type: file-system + ## Use binarystoreXml to provide a custom binarystore.xml + ## This is intentionally commented and below previous content of binarystoreXml is moved under files/binarystore.xml + ## binarystoreXml: + + ## For artifactory.persistence.type file-system + fileSystem: + ## Need to have the following set + existingSharedClaim: + enabled: false + numberOfExistingClaims: 1 + ## Should be a child directory of {{ .Values.artifactory.persistence.mountPath }} + dataDir: "{{ .Values.artifactory.persistence.mountPath }}/artifactory-data" + backupDir: "/var/opt/jfrog/artifactory-backup" + ## You may also use existing shared claims for the data and backup storage. This allows storage (NAS for example) to be used for Data and Backup dirs which are safe to share across multiple artifactory nodes. + ## You may specify numberOfExistingClaims to indicate how many of these existing shared claims to mount. (Default = 1) + ## Create PVCs with ReadWriteMany that match the naming convetions: + ## {{ template "artifactory-ha.fullname" . }}-data-pvc- + ## {{ template "artifactory-ha.fullname" . }}-backup-pvc + ## Example (using numberOfExistingClaims: 2) + ## myexample-data-pvc-0 + ## myexample-data-pvc-1 + ## myexample-backup-pvc + ## Note: While you need two PVC fronting two PVs, multiple PVs can be attached to the same storage in many cases allowing you to share an underlying drive. + ## For artifactory.persistence.type nfs + ## If using NFS as the shared storage, you must have a running NFS server that is accessible by your Kubernetes + ## cluster nodes. + ## Need to have the following set + nfs: + ## Must pass actual IP of NFS server with '--set For artifactory.persistence.nfs.ip=${NFS_IP}' + ip: + haDataMount: "/data" + haBackupMount: "/backup" + dataDir: "/var/opt/jfrog/artifactory-ha" + backupDir: "/var/opt/jfrog/artifactory-backup" + capacity: 200Gi + mountOptions: [] + ## For artifactory.persistence.type google-storage, google-storage-v2, google-storage-v2-direct + googleStorage: + ## When using GCP buckets as your binary store (Available with enterprise license only) + gcpServiceAccount: + enabled: false + ## Use either an existing secret prepared in advance or put the config (replace the content) in the values + ## ref: https://github.com/jfrog/charts/blob/master/stable/artifactory-ha/README.md#google-storage + # customSecretName: + # config: | + # { + # "type": "service_account", + # "project_id": "", + # "private_key_id": "?????", + # "private_key": "-----BEGIN PRIVATE KEY-----\n????????==\n-----END PRIVATE KEY-----\n", + # "client_email": "???@j.iam.gserviceaccount.com", + # "client_id": "???????", + # "auth_uri": "https://accounts.google.com/o/oauth2/auth", + # "token_uri": "https://oauth2.googleapis.com/token", + # "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + # "client_x509_cert_url": "https://www.googleapis.com/robot/v1....." + # } + endpoint: commondatastorage.googleapis.com + httpsOnly: false + ## Set a unique bucket name + bucketName: "artifactory-ha-gcp" + ## GCP Bucket Authentication with Identity and Credential is deprecated. + ## identity: + ## credential: + path: "artifactory-ha/filestore" + bucketExists: false + useInstanceCredentials: false + enableSignedUrlRedirect: false + ## For artifactory.persistence.type aws-s3-v3, s3-storage-v3-direct, s3-storage-v3-archive + awsS3V3: + testConnection: false + identity: + credential: + region: + bucketName: artifactory-aws + path: artifactory/filestore + endpoint: + port: + useHttp: + maxConnections: 50 + connectionTimeout: + socketTimeout: + kmsServerSideEncryptionKeyId: + kmsKeyRegion: + kmsCryptoMode: + useInstanceCredentials: true + usePresigning: false + signatureExpirySeconds: 300 + signedUrlExpirySeconds: 30 + cloudFrontDomainName: + cloudFrontKeyPairId: + cloudFrontPrivateKey: + enableSignedUrlRedirect: false + enablePathStyleAccess: false + multiPartLimit: + multipartElementSize: + ## For artifactory.persistence.type azure-blob, azure-blob-storage-direct, azure-blob-storage-v2-direct + azureBlob: + accountName: + accountKey: + endpoint: + containerName: + multiPartLimit: 100000000 + multipartElementSize: 50000000 + testConnection: false + service: + name: artifactory + type: ClusterIP + ## @param service.ipFamilyPolicy Controller Service ipFamilyPolicy (optional, cloud specific) + ## This can be either SingleStack, PreferDualStack or RequireDualStack + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilyPolicy: "" + ## @param service.ipFamilies Controller Service ipFamilies (optional, cloud specific) + ## This can be either ["IPv4"], ["IPv6"], ["IPv4", "IPv6"] or ["IPv6", "IPv4"] + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilies: [] + ## For supporting whitelist on the Artifactory service (useful if setting service.type=LoadBalancer) + ## Set this to a list of IP CIDR ranges + ## Example: loadBalancerSourceRanges: ['10.10.10.5/32', '10.11.10.5/32'] + ## or pass from helm command line + ## Example: helm install ... --set nginx.service.loadBalancerSourceRanges='{10.10.10.5/32,10.11.10.5/32}' + loadBalancerSourceRanges: [] + annotations: {} + ## Which nodes in the cluster should be in the external load balancer pool (have external traffic routed to them) + ## Supported pool values + ## members + ## all + pool: members + ## If the type is NodePort you can set a fixed port + # nodePort: 32082 + statefulset: + annotations: {} + ssh: + enabled: false + internalPort: 1339 + externalPort: 1339 + annotations: {} + ## Spread Artifactory pods evenly across your nodes or some other topology + ## Note this applies to both the primary and replicas + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app: '{{ template "artifactory-ha.name" . }}' + # role: '{{ template "artifactory-ha.name" . }}' + # release: "{{ .Release.Name }}" + + ## Type specific configurations. + ## There is a difference between the primary and the member nodes. + ## Customising their resources and java parameters is done here. + primary: + name: artifactory-ha-primary + ## preStartCommand specific to the primary node, to be run after artifactory.preStartCommand + # preStartCommand: + labels: {} + persistence: + ## Set existingClaim to true or false + ## If true, you must prepare a PVC with the name e.g `volume-myrelease-artifactory-ha-primary-0` + existingClaim: false + replicaCount: 3 + # minAvailable: 1 + + updateStrategy: + type: RollingUpdate + ## Resources for the primary node + resources: {} + # requests: + # memory: "1Gi" + # cpu: "500m" + # limits: + # memory: "2Gi" + # cpu: "1" + ## The following Java options are passed to the java process running Artifactory primary node. + ## You should set them according to the resources set above + javaOpts: + # xms: "1g" + # xmx: "2g" + # corePoolSize: 24 + jmx: + enabled: false + port: 9010 + host: + ssl: false + # When authenticate is true, accessFile and passwordFile are required + authenticate: false + accessFile: + passwordFile: + # other: "" + nodeSelector: {} + tolerations: [] + affinity: {} + ## Only used if "affinity" is empty + podAntiAffinity: + ## Valid values are "soft" or "hard"; any other value indicates no anti-affinity + type: "soft" + topologyKey: "kubernetes.io/hostname" + node: + name: artifactory-ha-member + ## preStartCommand specific to the member node, to be run after artifactory.preStartCommand + # preStartCommand: + labels: {} + persistence: + ## Set existingClaim to true or false + ## If true, you must prepare a PVC with the name e.g `volume-myrelease-artifactory-ha-member-0` + existingClaim: false + replicaCount: 0 + updateStrategy: + type: RollingUpdate + minAvailable: 1 + ## Resources for the member nodes + resources: {} + # requests: + # memory: "1Gi" + # cpu: "500m" + # limits: + # memory: "2Gi" + # cpu: "1" + ## The following Java options are passed to the java process running Artifactory member nodes. + ## You should set them according to the resources set above + javaOpts: + # xms: "1g" + # xmx: "2g" + # corePoolSize: 24 + jmx: + enabled: false + port: 9010 + host: + ssl: false + # When authenticate is true, accessFile and passwordFile are required + authenticate: false + accessFile: + passwordFile: + # other: "" + nodeSelector: {} + ## Wait for Artifactory primary + waitForPrimaryStartup: + enabled: true + ## Setting time will override the built in test and will just wait the set time + time: + tolerations: [] + ## Complete specification of the "affinity" of the member nodes; if this is non-empty, + ## "podAntiAffinity" values are not used. + affinity: {} + ## Only used if "affinity" is empty + podAntiAffinity: + ## Valid values are "soft" or "hard"; any other value indicates no anti-affinity + type: "soft" + topologyKey: "kubernetes.io/hostname" +frontend: + name: frontend + enabled: true + internalPort: 8070 + ## Extra environment variables that can be used to tune frontend to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: MY_ENV_VAR + # value: "" + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "1Gi" + # cpu: "1" + ## Session settings + session: + ## Time in minutes after which the frontend token will need to be refreshed + timeoutMinutes: '30' + ## Add lifecycle hooks for frontend container + lifecycle: {} + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + # preStop: + # exec: + # command: ["/bin/sh","-c","echo Hello from the preStop handler"] + + ## The following settings are to configure the frequency of the liveness and startup probes when splitServicesToContainers set to true + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:{{ .Values.frontend.internalPort }}/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " --cert=ca.crt --key=ca.private.key` + # customCertificatesSecretName: + + ## When resetAccessCAKeys is true, Access will regenerate the CA certificate and matching private key + # resetAccessCAKeys: false + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 50 + sendReasonPhrase: false + extraConfig: 'acceptCount="100"' + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:8040/access/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " /var/opt/jfrog/nginx/message"] + # preStop: + # exec: + # command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"] + + ## Sidecar containers for tailing Nginx logs + loggers: [] + # - access.log + # - error.log + + ## Loggers containers resources + loggersResources: {} + # requests: + # memory: "64Mi" + # cpu: "25m" + # limits: + # memory: "128Mi" + # cpu: "50m" + + ## Logs options + logs: + stderr: false + stdout: false + level: warn + ## A list of custom ports to expose on the NGINX pod. Follows the conventional Kubernetes yaml syntax for container ports. + customPorts: [] + # - containerPort: 8066 + # name: docker + + ## The nginx main conf was moved to files/nginx-main-conf.yaml. This key is commented out to keep support for the old configuration + # mainConf: | + + ## The nginx artifactory conf was moved to files/nginx-artifactory-conf.yaml. This key is commented out to keep support for the old configuration + # artifactoryConf: | + customInitContainers: "" + customSidecarContainers: "" + customVolumes: "" + customVolumeMounts: "" + customCommand: + ## allows overwriting the command for the nginx container. + ## defaults to [ 'nginx', '-g', 'daemon off;' ] + + service: + ## For minikube, set this to NodePort, elsewhere use LoadBalancer + type: LoadBalancer + ssloffload: false + ## @param service.ipFamilyPolicy Controller Service ipFamilyPolicy (optional, cloud specific) + ## This can be either SingleStack, PreferDualStack or RequireDualStack + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilyPolicy: "" + ## @param service.ipFamilies Controller Service ipFamilies (optional, cloud specific) + ## This can be either ["IPv4"], ["IPv6"], ["IPv4", "IPv6"] or ["IPv6", "IPv4"] + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilies: [] + ## For supporting whitelist on the Nginx LoadBalancer service + ## Set this to a list of IP CIDR ranges + ## Example: loadBalancerSourceRanges: ['10.10.10.5/32', '10.11.10.5/32'] + ## or pass from helm command line + ## Example: helm install ... --set nginx.service.loadBalancerSourceRanges='{10.10.10.5/32,10.11.10.5/32}' + loadBalancerSourceRanges: [] + ## Provide static ip address + loadBalancerIP: + ## There are two available options: "Cluster" (default) and "Local". + externalTrafficPolicy: Cluster + labels: {} + # label-key: label-value + ## If the type is NodePort you can set a fixed port + # nodePort: 32082 + ## A list of custom ports to be exposed on nginx service. Follows the conventional Kubernetes yaml syntax for service ports. + customPorts: [] + # - port: 8066 + # targetPort: 8066 + # protocol: TCP + # name: docker + + annotations: {} + ## Renamed nginx internalPort 80,443 to 8080,8443 to support openshift + http: + enabled: true + externalPort: 80 + internalPort: 8080 + https: + enabled: true + externalPort: 443 + internalPort: 8443 + ## DEPRECATED: The following will be replaced by L1065-L1076 in a future release + # externalPortHttp: 80 + # internalPortHttp: 8080 + # externalPortHttps: 443 + # internalPortHttps: 8443 + + ssh: + internalPort: 1339 + externalPort: 1339 + ## The following settings are to configure the frequency of the liveness and readiness probes. + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} {{ include "nginx.scheme" . }}://localhost:{{ include "nginx.port" . }}/ + initialDelaySeconds: {{ if semverCompare " + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClassName: "-" + resources: {} + # requests: + # memory: "250Mi" + # cpu: "100m" + # limits: + # memory: "250Mi" + # cpu: "500m" + + nodeSelector: {} + tolerations: [] + affinity: {} +## Filebeat Sidecar container +## The provided filebeat configuration is for Artifactory logs. It assumes you have a logstash installed and configured properly. +filebeat: + enabled: false + name: artifactory-filebeat + image: + repository: "docker.elastic.co/beats/filebeat" + version: 7.16.2 + logstashUrl: "logstash:5044" + terminationGracePeriod: 10 + livenessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + curl --fail 127.0.0.1:5066 + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + filebeat test output + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "100Mi" + # cpu: "100m" + + filebeatYml: | + logging.level: info + path.data: {{ .Values.artifactory.persistence.mountPath }}/log/filebeat + name: artifactory-filebeat + queue.spool: + file: + permissions: 0760 + filebeat.inputs: + - type: log + enabled: true + close_eof: ${CLOSE:false} + paths: + - {{ .Values.artifactory.persistence.mountPath }}/log/*.log + fields: + service: "jfrt" + log_type: "artifactory" + output: + logstash: + hosts: ["{{ .Values.filebeat.logstashUrl }}"] +## Allows to add additional kubernetes resources +## Use --- as a separator between multiple resources +## For an example, refer - https://github.com/jfrog/log-analytics-prometheus/blob/master/helm/artifactory-ha-values.yaml +additionalResources: "" +## Adding entries to a Pod's /etc/hosts file +## For an example, refer - https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases +hostAliases: [] +# - ip: "127.0.0.1" +# hostnames: +# - "foo.local" +# - "bar.local" +# - ip: "10.1.2.3" +# hostnames: +# - "foo.remote" +# - "bar.remote" + +## Toggling this feature is seamless and requires helm upgrade +## will enable all microservices to run in different containers in a single pod (by default it is true) +splitServicesToContainers: true +## Specify common probes parameters +probes: + timeoutSeconds: 5 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/CHANGELOG.md b/charts/jfrog/artifactory-jcr/107.90.9/CHANGELOG.md new file mode 100644 index 0000000000..65ca8bcf76 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/CHANGELOG.md @@ -0,0 +1,206 @@ +# JFrog Container Registry Chart Changelog +All changes to this chart will be documented in this file. + +## [107.90.9] - Feb 20, 2024 +* Updated `artifactory.installerInfo` content + +## [107.80.0] - Feb 1, 2024 +* Updated README.md to create a namespace using `--create-namespace` as part of helm install + +## [107.74.0] - Nov 23, 2023 +* **IMPORTANT** +* Added min kubeVersion ">= 1.19.0-0" in chart.yaml + +## [107.66.0] - Jul 20, 2023 +* Disabled federation services when splitServicesToContainers=true + +## [107.45.0] - Aug 25, 2022 +* Included event service as mandatory and remove the flag from values.yaml + +## [107.41.0] - Jul 22, 2022 +* Bumping chart version to align with app version +* Disabled jfconnect and event services when splitServicesToContainers=true + +## [107.19.4] - May 27, 2021 +* Bumping chart version to align with app version +* Update dependency Artifactory chart version to 107.19.4 + +## [4.0.0] - Apr 22, 2021 +* **Breaking change:** +* Increased default postgresql persistence size to `200Gi` +* Update postgresql tag version to `13.2.0-debian-10-r55` +* Update postgresql chart version to `10.3.18` in chart.yaml - [10.x Upgrade Notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1000) +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x/12.x's postgresql.image.tag, previous postgresql.persistence.size and databaseUpgradeReady=true +* **IMPORTANT** +* This chart is only helm v3 compatible. +* Update dependency Artifactory chart version to 12.0.0 (Artifactory 7.18.3) + +## [3.8.0] - Apr 5, 2021 +* **IMPORTANT** +* Added `charts.jfrog.io` as default JFrog Helm repository +* Update dependency Artifactory chart version to 11.13.0 (Artifactory 7.17.5) + +## [3.7.0] - Mar 31, 2021 +* Update dependency Artifactory chart version to 11.12.2 (Artifactory 7.17.4) + +## [3.6.0] - Mar 15, 2021 +* Update dependency Artifactory chart version to 11.10.0 (Artifactory 7.16.3) + +## [3.5.1] - Mar 03, 2021 +* Update dependency Artifactory chart version to 11.9.3 (Artifactory 7.15.4) + +## [3.5.0] - Feb 18, 2021 +* Update dependency Artifactory chart version to 11.9.0 (Artifactory 7.15.3) + +## [3.4.1] - Feb 08, 2021 +* Update dependency Artifactory chart version to 11.8.0 (Artifactory 7.12.8) + +## [3.4.0] - Jan 4, 2020 +* Update dependency Artifactory chart version to 11.7.4 (Artifactory 7.12.5) + +## [3.3.1] - Dec 1, 2020 +* Update dependency Artifactory chart version to 11.5.4 (Artifactory 7.11.5) + +## [3.3.0] - Nov 23, 2020 +* Update dependency Artifactory chart version to 11.5.2 (Artifactory 7.11.2) + +## [3.2.2] - Nov 9, 2020 +* Update dependency Artifactory chart version to 11.4.5 (Artifactory 7.10.6) + +## [3.2.1] - Nov 2, 2020 +* Update dependency Artifactory chart version to 11.4.4 (Artifactory 7.10.5) + +## [3.2.0] - Oct 19, 2020 +* Update dependency Artifactory chart version to 11.4.0 (Artifactory 7.10.2) + +## [3.1.0] - Sep 30, 2020 +* Update dependency Artifactory chart version to 11.1.0 (Artifactory 7.9.0) + +## [3.0.2] - Sep 23, 2020 +* Updates readme + +## [3.0.1] - Sep 15, 2020 +* Update dependency Artifactory chart version to 11.0.1 (Artifactory 7.7.8) + +## [3.0.0] - Sep 14, 2020 +* **Breaking change:** Added `image.registry` and changed `image.version` to `image.tag` for docker images +* Update dependency Artifactory chart version to 11.0.0 (Artifactory 7.7.3) + +## [2.5.1] - Jul 29, 2020 +* Update dependency Artifactory chart version to 10.0.12 (Artifactory 7.6.3) + +## [2.5.0] - Jul 10, 2020 +* Update dependency Artifactory chart version to 10.0.3 (Artifactory 7.6.2) +* **IMPORTANT** +* Added ChartCenter Helm repository in README + +## [2.4.0] - Jun 30, 2020 +* Update dependency Artifactory chart version to 9.6.0 (Artifactory 7.6.1) + +## [2.3.1] - Jun 12, 2020 +* Update dependency Artifactory chart version to 9.5.2 (Artifactory 7.5.7) + +## [2.3.0] - Jun 1, 2020 +* Update dependency Artifactory chart version to 9.5.0 (Artifactory 7.5.5) + +## [2.2.5] - May 27, 2020 +* Update dependency Artifactory chart version to 9.4.9 (Artifactory 7.4.3) + +## [2.2.4] - May 20, 2020 +* Update dependency Artifactory chart version to 9.4.6 (Artifactory 7.4.3) + +## [2.2.3] - May 07, 2020 +* Update dependency Artifactory chart version to 9.4.5 (Artifactory 7.4.3) +* Add `installerInfo` string format + +## [2.2.2] - Apr 28, 2020 +* Update dependency Artifactory chart version to 9.4.4 (Artifactory 7.4.3) + +## [2.2.1] - Apr 27, 2020 +* Update dependency Artifactory chart version to 9.4.3 (Artifactory 7.4.1) + +## [2.2.0] - Apr 14, 2020 +* Update dependency Artifactory chart version to 9.4.0 (Artifactory 7.4.1) + +## [2.2.0] - Apr 14, 2020 +* Update dependency Artifactory chart version to 9.4.0 (Artifactory 7.4.1) + +## [2.1.6] - Apr 13, 2020 +* Update dependency Artifactory chart version to 9.3.1 (Artifactory 7.3.2) + +## [2.1.5] - Apr 8, 2020 +* Update dependency Artifactory chart version to 9.2.8 (Artifactory 7.3.2) + +## [2.1.4] - Mar 30, 2020 +* Update dependency Artifactory chart version to 9.2.3 (Artifactory 7.3.2) + +## [2.1.3] - Mar 30, 2020 +* Update dependency Artifactory chart version to 9.2.1 (Artifactory 7.3.2) + +## [2.1.2] - Mar 26, 2020 +* Update dependency Artifactory chart version to 9.1.5 (Artifactory 7.3.2) + +## [2.1.1] - Mar 25, 2020 +* Update dependency Artifactory chart version to 9.1.4 (Artifactory 7.3.2) + +## [2.1.0] - Mar 23, 2020 +* Update dependency Artifactory chart version to 9.1.3 (Artifactory 7.3.2) + +## [2.0.13] - Mar 19, 2020 +* Update dependency Artifactory chart version to 9.0.28 (Artifactory 7.2.1) + +## [2.0.12] - Mar 17, 2020 +* Update dependency Artifactory chart version to 9.0.26 (Artifactory 7.2.1) + +## [2.0.11] - Mar 11, 2020 +* Unified charts public release + +## [2.0.10] - Mar 8, 2020 +* Update dependency Artifactory chart version to 9.0.20 (Artifactory 7.2.1) + +## [2.0.9] - Feb 26, 2020 +* Update dependency Artifactory chart version to 9.0.15 (Artifactory 7.2.1) + +## [2.0.0] - Feb 12, 2020 +* Update dependency Artifactory chart version to 9.0.0 (Artifactory 7.0.0) + +## [1.1.0] - Jan 19, 2020 +* Update dependency Artifactory chart version to 8.4.1 (Artifactory 6.17.0) + +## [1.1.1] - Feb 3, 2020 +* Update dependency Artifactory chart version to 8.4.4 + +## [1.1.0] - Jan 19, 2020 +* Update dependency Artifactory chart version to 8.4.1 (Artifactory 6.17.0) + +## [1.0.1] - Dec 31, 2019 +* Update dependency Artifactory chart version to 8.3.5 + +## [1.0.0] - Dec 23, 2019 +* Update dependency Artifactory chart version to 8.3.3 + +## [0.2.1] - Dec 12, 2019 +* Update dependency Artifactory chart version to 8.3.1 + +## [0.2.0] - Dec 1, 2019 +* Updated Artifactory version to 6.16.0 + +## [0.1.5] - Nov 28, 2019 +* Update dependency Artifactory chart version to 8.2.6 + +## [0.1.4] - Nov 20, 2019 +* Update Readme + +## [0.1.3] - Nov 20, 2019 +* Fix JCR logo url +* Update dependency to Artifactory 8.2.2 chart + +## [0.1.2] - Nov 20, 2019 +* Update JCR logo + +## [0.1.1] - Nov 20, 2019 +* Add `appVersion` to Chart.yaml + +## [0.1.0] - Nov 20, 2019 +* Initial release of the JFrog Container Registry helm chart diff --git a/charts/jfrog/artifactory-jcr/107.90.9/Chart.yaml b/charts/jfrog/artifactory-jcr/107.90.9/Chart.yaml new file mode 100644 index 0000000000..20af24c78b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/Chart.yaml @@ -0,0 +1,30 @@ +annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: JFrog Container Registry + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: artifactory-jcr +apiVersion: v2 +appVersion: 7.90.9 +dependencies: +- name: artifactory + repository: file://./charts/artifactory + version: 107.90.9 +description: JFrog Container Registry +home: https://jfrog.com/container-registry/ +icon: file://assets/icons/artifactory-jcr.png +keywords: +- artifactory +- jfrog +- container +- registry +- devops +- jfrog-container-registry +kubeVersion: '>= 1.19.0-0' +maintainers: +- email: helm@jfrog.com + name: Chart Maintainers at JFrog +name: artifactory-jcr +sources: +- https://github.com/jfrog/charts +type: application +version: 107.90.9 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/LICENSE b/charts/jfrog/artifactory-jcr/107.90.9/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/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/charts/jfrog/artifactory-jcr/107.90.9/README.md b/charts/jfrog/artifactory-jcr/107.90.9/README.md new file mode 100644 index 0000000000..c0051e61d1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/README.md @@ -0,0 +1,125 @@ +# JFrog Container Registry Helm Chart + +JFrog Container Registry is a free Artifactory edition with Docker and Helm repositories support. + +**Heads up: Our Helm Chart docs are moving to our main documentation site. For Artifactory installers, see [Installing Artifactory](https://www.jfrog.com/confluence/display/JFROG/Installing+Artifactory).** + +## Prerequisites Details + +* Kubernetes 1.19+ + +## Chart Details +This chart will do the following: + +* Deploy JFrog Container Registry +* Deploy an optional Nginx server +* Deploy an optional PostgreSQL Database +* Optionally expose Artifactory with Ingress [Ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/) + +## Installing the Chart + +### Add JFrog Helm repository + +Before installing JFrog helm charts, you need to add the [JFrog helm repository](https://charts.jfrog.io) to your helm client. + +```bash +helm repo add jfrog https://charts.jfrog.io +helm repo update +``` + +### Install Chart +To install the chart with the release name `jfrog-container-registry`: +```bash +helm upgrade --install jfrog-container-registry --set artifactory.postgresql.postgresqlPassword= jfrog/artifactory-jcr --namespace artifactory-jcr --create-namespace +``` + +### Accessing JFrog Container Registry +**NOTE:** If using artifactory or nginx service type `LoadBalancer`, it might take a few minutes for JFrog Container Registry's public IP to become available. + +### Updating JFrog Container Registry +Once you have a new chart version, you can upgrade your deployment with +```bash +helm upgrade jfrog-container-registry jfrog/artifactory-jcr --namespace artifactory-jcr --create-namespace +``` + +### Special Upgrade Notes +#### Artifactory upgrade from 6.x to 7.x (App Version) +Arifactory 6.x to 7.x upgrade requires a one time migration process. This is done automatically on pod startup if needed. +It's possible to configure the migration timeout with the following configuration in extreme cases. The provided default should be more than enough for completion of the migration. +```yaml +artifactory: + artifactory: + # Migration support from 6.x to 7.x + migration: + enabled: true + timeoutSeconds: 3600 +``` +* Note: If you are upgrading from 1.x to 3.x and above chart versions, please delete the existing statefulset of postgresql before upgrading the chart due to breaking changes in postgresql subchart. +```bash +kubectl delete statefulsets -postgresql +``` +* For more details about artifactory chart upgrades refer [here](https://github.com/jfrog/charts/blob/master/stable/artifactory/UPGRADE_NOTES.md) + +### Deleting JFrog Container Registry + +```bash +helm delete jfrog-container-registry --namespace artifactory-jcr +``` + +This will delete your JFrog Container Registry deployment.
+**NOTE:** You might have left behind persistent volumes. You should explicitly delete them with +```bash +kubectl delete pvc ... +kubectl delete pv ... +``` + +## Database +The JFrog Container Registry chart comes with PostgreSQL deployed by default.
+For details on the PostgreSQL configuration or customising the database, Look at the options described in the [Artifactory helm chart](https://github.com/jfrog/charts/tree/master/stable/artifactory). + +### Ingress and TLS +To get Helm to create an ingress object with a hostname, add these two lines to your Helm command: +```bash +helm upgrade --install jfrog-container-registry \ + --set artifactory.nginx.enabled=false \ + --set artifactory.ingress.enabled=true \ + --set artifactory.ingress.hosts[0]="artifactory.company.com" \ + --set artifactory.artifactory.service.type=NodePort \ + jfrog/artifactory-jcr --namespace artifactory-jcr --create-namespace +``` + +To manually configure TLS, first create/retrieve a key & certificate pair for the address(es) you wish to protect. Then create a TLS secret in the namespace: + +```bash +kubectl create secret tls artifactory-tls --cert=path/to/tls.cert --key=path/to/tls.key +``` + +Include the secret's name, along with the desired hostnames, in the Artifactory Ingress TLS section of your custom `values.yaml` file: + +```yaml +artifactory: + artifactory: + ingress: + ## If true, Artifactory Ingress will be created + ## + enabled: true + + ## Artifactory Ingress hostnames + ## Must be provided if Ingress is enabled + ## + hosts: + - jfrog-container-registry.domain.com + annotations: + kubernetes.io/tls-acme: "true" + ## Artifactory Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: + - secretName: artifactory-tls + hosts: + - jfrog-container-registry.domain.com +``` + +## Useful links +https://www.jfrog.com +https://www.jfrog.com/confluence/ diff --git a/charts/jfrog/artifactory-jcr/107.90.9/app-readme.md b/charts/jfrog/artifactory-jcr/107.90.9/app-readme.md new file mode 100644 index 0000000000..9d9b7d85fc --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/app-readme.md @@ -0,0 +1,18 @@ +# JFrog Container Registry Helm Chart + +Universal Repository Manager supporting all major packaging formats, build tools and CI servers. + +## Chart Details +This chart will do the following: + +* Deploy JFrog Container Registry +* Deploy an optional Nginx server +* Optionally expose Artifactory with Ingress [Ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/) + + +## Useful links +Blog: [Herd Trust Into Your Rancher Labs Multi-Cloud Strategy with Artifactory](https://jfrog.com/blog/herd-trust-into-your-rancher-labs-multi-cloud-strategy-with-artifactory/) + +## Activate Your Artifactory Instance +Don't have a license? Please send an email to [rancher-jfrog-licenses@jfrog.com](mailto:rancher-jfrog-licenses@jfrog.com) to get it. + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/.helmignore b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/.helmignore new file mode 100644 index 0000000000..b6e97f07fb --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +OWNERS + +tests/ \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/CHANGELOG.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/CHANGELOG.md new file mode 100644 index 0000000000..aa71f9f43b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/CHANGELOG.md @@ -0,0 +1,1365 @@ +# JFrog Artifactory Chart Changelog +All changes to this chart will be documented in this file. + +## [107.90.9] - July 18, 2024 +* Fixed #adding colon in image registry which breaks deployment [GH-1892](https://github.com/jfrog/charts/pull/1892) +* Added new `nginx.hosts` to use Nginx server_name directive instead of `ingress.hosts` +* Added a deprecation notice of ingress.hosts when `ngnix.enabled` is true +* Added new evidence service +* Corrected database connection values based on sizing +* **IMPORTANT** +* Separate access from artifactory tomcat to run on its own dedicated tomcat + * With this change access will be running in its own dedicated container + * This will give the ability to control resources and java options specific to access + Can be done by passing the following, + `access.javaOpts.other` + `access.resources` + `access.extraEnvironmentVariables` +* Updating the example link for downloading the DB driver +* Added Binary Provider recommendations + +## [107.89.0] - June 7, 2024 +* Fix the indentation of the commented-out sections in the values.yaml file +* Fixed sizing values by removing `JF_SHARED_NODE_HAENABLED` in xsmall/small configurations + +## [107.88.0] - May 29, 2024 +* **IMPORTANT** +* Refactored `nginx.artifactoryConf` and `nginx.mainConf` configuration (moved to files/nginx-artifactory-conf.yaml and files/nginx-main-conf.yaml instead of keys in values.yaml) + +## [107.87.0] - May 29, 2024 +* Renamed `.Values.artifactory.openMetrics` to `.Values.artifactory.metrics` + +## [107.85.0] - May 29, 2024 +* Changed `migration.enabled` to false by default. For 6.x to 7.x migration, this flag needs to be set to `true` + +## [107.84.0] - May 29, 2024 +* Added image section for `initContainers` instead of `initContainerImage` +* Renamed `router.image.imagePullPolicy` to `router.image.pullPolicy` +* Removed image section for `loggers` +* Added support for `global.verisons.initContainers` to override `initContainers.image.tag` +* Fixed an issue with extraSystemYaml merge +* **IMPORTANT** +* Renamed `artifactory.setSecurityContext` to `artifactory.podSecurityContext` +* Renamed `artifactory.uid` to `artifactory.podSecurityContext.runAsUser` +* Renamed `artifactory.gid` to `artifactory.podSecurityContext.runAsGroup` and `artifactory.podSecurityContext.fsGroup` +* Renamed `artifactory.fsGroupChangePolicy` to `artifactory.podSecurityContext.fsGroupChangePolicy` +* Renamed `artifactory.seLinuxOptions` to `artifactory.podSecurityContext.seLinuxOptions` +* Added flag `allowNonPostgresql` defaults to false +* Update postgresql tag version to `15.6.0-debian-12-r5` +* Added a check if `initContainerImage` exists +* Fixed an issue to generate unified secret to support artifactory fullname [GH-1882](https://github.com/jfrog/charts/issues/1882) +* Fixed an issue template render on loggers [GH-1883](https://github.com/jfrog/charts/issues/1883) +* Fixed resource constraints for "setup" initContainer of nginx deployment [GH-962] (https://github.com/jfrog/charts/issues/962) +* Added .Values.artifactory.unifiedSecretPrependReleaseName` for unified secret to prepend release name +* Fixed maxCacheSize and cacheProviderDir mix up under azure-blob-storage-v2-direct template in binarystore.xml + +## [107.82.0] - Mar 04, 2024 +* Added `disableRouterBypass` flag as experimental feature, to disable the artifactoryPath /artifactory/ and route all traffic through the Router. +* Removed Replicator service + +## [107.81.0] - Feb 20, 2024 +* **IMPORTANT** +* Refactored systemYaml configuration (moved to files/system.yaml instead of key in values.yaml) +* Added ability to provide `extraSystemYaml` configuration in values.yaml which will merge with the existing system yaml when `systemYamlOverride` is not given [GH-1848](https://github.com/jfrog/charts/pull/1848) +* Added option to modify the new cache configs, maxFileSizeLimit and skipDuringUpload +* Added IPV4/IPV6 Dualstack flag support for Artifactory and nginx service +* Added `singleStackIPv6Cluster` flag, which manages the Nginx configuration to enable listening on IPv6 and proxying. +* Fixing broken link for creating additional kubernetes resources. Refer [here](https://github.com/jfrog/log-analytics-prometheus/blob/master/helm/artifactory-values.yaml) +* Refactored installerInfo configuration (moved to files/installer-info.json instead of key in values.yaml) + +## [107.80.0] - Feb 20, 2024 +* Updated README.md to create a namespace using `--create-namespace` as part of helm install + +## [107.79.0] - Feb 20, 2024 +* **IMPORTANT** +* Added `unifiedSecretInstallation` flag which enables single unified secret holding all internal (chart) secrets to `true` by default +* Added support for azure-blob-storage-v2-direct config +* Added option to set Nginx to write access_log to container STDOUT +* **Important change:** +* Update postgresql tag version to `15.2.0-debian-11-r23` +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default bundles PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x/12.x/13.x's postgresql.image.tag, previous postgresql.persistence.size and databaseUpgradeReady=true + +## [107.77.0] - April 22, 2024 +* Removed integration service +* Added recommended postgresql sizing configurations under sizing directory +* Updated artifactory-federation (probes, port, embedded mode) +* Fixed - Removed duplicate keys of the sizing yaml file +* Fixing broken nginx port [GH-1860](https://github.com/jfrog/charts/issues/1860) +* Added nginx.customCommand to use custom commands for the nginx container + +## [107.76.0] - Dec 13, 2023 +* Added connectionTimeout and socketTimeout paramaters under AWSS3 binarystore section +* Reduced nginx startupProbe initialDelaySeconds + +## [107.74.0] - Nov 30, 2023 +* Added recommended sizing configurations under sizing directory, please refer [here](README.md/#apply-sizing-configurations-to-the-chart) +* **IMPORTANT** +* Added min kubeVersion ">= 1.19.0-0" in chart.yaml + +## [107.70.0] - Nov 30, 2023 +* Fixed - StatefulSet pod annotations changed from range to toYaml [GH-1828](https://github.com/jfrog/charts/issues/1828) +* Fixed - Invalid format for awsS3V3 `multiPartLimit,multipartElementSize` in binarystore.xml. +* Fixed - SecurityContext with runAsGroup in artifactory [GH-1838](https://github.com/jfrog/charts/issues/1838) +* Added support for custom labels in the Nginx pods [GH-1836](https://github.com/jfrog/charts/pull/1836) +* Added podSecurityContext and containerSecurityContext for nginx +* Added support for nginx on openshift, set `podSecurityContext` and `containerSecurityContext` to false +* Renamed nginx internalPort 80,443 to 8080,8443 to support openshift + +## [107.69.0] - Sep 18, 2023 +* Adjust rtfs context +* Fixed - Metadata service does not respect customVolumeMounts for DB CAs [GH-1815](https://github.com/jfrog/charts/issues/1815) + +## [107.68.8] - Sep 18, 2023 +* Reverted - Enabled `unifiedSecretInstallation` by default [GH-1819](https://github.com/jfrog/charts/issues/1819) +* Removed openshift condition check from NOTES.txt + +## [107.68.7] - Aug 28, 2023 +* Enabled `unifiedSecretInstallation` by default + +## [107.67.0] - Aug 28, 2023 +* Add 'extraJavaOpts' and 'port' values to federation service + +## [107.66.0] - Aug 28, 2023 +* Added federation service container in artifactory +* Add rtfs service to ingress in artifactory + +## [107.64.0] - Aug 28, 2023 +* Added support to configure event.webhooks within generated system.yaml +* Fixed an issue to generate ssl certificate should support artifactory fullname +* Added binarystore.xml template to persistence storage type `nfs`. The default Filestore location configured according to artifactory.persistence.nfs.dataDir. +* Added 'multiPartLimit' and 'multipartElementSize' parameters to awsS3V3 binary providers. +* Increased default Artifactory Tomcat acceptCount config to 400 +* Fixed Illegal Strict-Transport-Security header in nginx config + +## [107.63.0] - Aug 28, 2023 +* Added support for Openshift by adding the securityContext in container level. +* **IMPORTANT** +* Disable securityContext in container and pod level to deploy postgres on openshift. +* Fixed support for fsGroup in non openshift environemnt and runAsGroup in openshift environment. +* Fixed - Helm Template Error when using artifactory.loggers [GH-1791](https://github.com/jfrog/charts/issues/1791) +* Removed the nginx disable condition for openshift +* Fixed jfconnect disabling as micro-service on splitcontainers [GH-1806](https://github.com/jfrog/charts/issues/1806) + +## [107.62.0] - Jun 5, 2023 +* Upgraded to autoscaling/v2 +* Added support for 'port' and 'useHttp' parameters for s3-storage-v3 binary provider [GH-1767](https://github.com/jfrog/charts/issues/1767) + +## [107.61.0] - May 31, 2023 +* Added new binary provider `google-storage-v2-direct` +* Added missing parameter 'enableSignedUrlRedirect' to 'googleStorage' + +## [107.60.0] - May 31, 2023 +* Enabled `splitServicesToContainers` to true by default +* Updated the recommended values for small, medium and large installations to support the 'splitServicesToContainers' + +## [107.59.0] - May 31, 2023 +* Fixed reference of `terminationGracePeriodSeconds` +* Added Support for Cold Artifact Storage as part of the systemYaml configuration (disabled by default) +* Added new binary provider `s3-storage-v3-archive` +* Fixed jfconnect disabling as micro-service on non-splitcontainers +* Fixed wrong cache-fs provider ID of cluster-s3-storage-v3 in the binarystore.xml [GH-1772](https://github.com/jfrog/charts/issues/1772) + +## [107.58.0] - Mar 23, 2023 +* Updated postgresql multi-arch tag version to `13.10.0-debian-11-r14` +* Removed obselete remove-lost-found initContainer` +* Added env JF_SHARED_NODE_HAENABLED under frontend when running in the container split mode + +## [107.57.0] - Mar 02, 2023 +* Updated initContainerImage and logger image to `ubi9/ubi-minimal:9.1.0.1793` + +## [107.55.0] - Jan 31, 2023 +* Updated initContainerImage and logger image to `ubi9/ubi-minimal:9.1.0.1760` +* Adding a custom preStop to Artifactory router for allowing graceful termination to complete + +## [107.53.0] - Jan 20, 2023 +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.7.1049` + +## [107.50.0] - Jan 20, 2023 +* Updated postgresql tag version to `13.9.0-debian-11-11` +* Fixed an issue for capabilities check of ingress +* Updated jfrogUrl text path in migrate.sh file +* Added a note that from 107.46.x chart versions, `copyOnEveryStartup` is not needed for binarystore.xml, it is always copied via initContainers. For more Info, Refer [GH-1723](https://github.com/jfrog/charts/issues/1723) + +## [107.49.0] - Jan 16, 2023 +* Added support for setting `seLinuxOptions` in `securityContext` [GH-1699](https://github.com/jfrog/charts/pull/1699) +* Added option to enable/disable proxy_request_buffering and proxy_buffering_off [GH-1686](https://github.com/jfrog/charts/pull/1686) +* Updated initContainerImage and logger image to `ubi8/ubi-minimal:8.7.1049` + +## [107.48.0] - Oct 27, 2022 +* Updated router version to `7.51.0` + +## [107.47.0] - Sep 29, 2022 +* Updated initContainerImage to `ubi8/ubi-minimal:8.6-941` +* Added support for annotations for artifactory statefulset and nginx deployment [GH-1665](https://github.com/jfrog/charts/pull/1665) +* Updated router version to `7.49.0` + +## [107.46.0] - Sep 14, 2022 +* **IMPORTANT** +* Added support for lifecycle hooks for all containers, changed `artifactory.postStartCommand` to `.Values.artifactory.lifecycle.postStart.exec.command` +* Updated initContainerImage to `ubi8/ubi-minimal:8.6-902` +* Update nginx configuration to allow websocket requests when using pipelines +* Fixed an issue to allow artifactory to make direct API calls to store instead via jfconnect service when `splitServicesToContainers=true` +* Refactor binarystore.xml configuration (moved to `files/binarystore.xml` instead of key in values.yaml) +* Added new binary providers `cluster-s3-storage-v3`, `s3-storage-v3-direct`, `azure-blob-storage-direct`, `google-storage-v2` +* Deprecated (removed) `aws-s3` binary provider [JetS3t library](https://www.jfrog.com/confluence/display/JFROG/Configuring+the+Filestore#ConfiguringtheFilestore-BinaryProvider) +* Deprecated (removed) `google-storage` binary provider and force persistence storage type `google-storage` to work with `google-storage-v2` only +* Copy binarystore.xml in init Container to fix existing persistence on file system in clear text +* Removed obselete `.Values.artifactory.binarystore.enabled` key +* Removed `newProbes.enabled`, default to new probes +* Added nginx.customCommand using inotifyd to reload nginx's config upon ssl secret or configmap changes [GH-1640](https://github.com/jfrog/charts/pull/1640) + +## [107.43.0] - Aug 25, 2022 +* Added flag `artifactory.replicator.ingress.enabled` to enable/disable ingress for replicator +* Updated initContainerImage to `ubi8/ubi-minimal:8.6-854` +* Updated router version to `7.45.0` +* Added flag `artifactory.schedulerName` to set for the pods the value of schedulerName field [GH-1606](https://github.com/jfrog/charts/issues/1606) +* Enabled TLS based on access or router in values.yaml + +## [107.42.0] - Aug 25, 2022 +* Enabled database creds secret to use from unified secret +* Updated router version to `7.42.0` +* Fix duplicate volumes for userPluginSecrets [GH-1650] (https://github.com/jfrog/charts/issues/1650) +* Added support to truncate (> 63 chars) for unifiedCustomSecretVolumeName + +## [107.41.0] - June 27, 2022 +* Added support for nginx.terminationGracePeriodSeconds [GH-1645](https://github.com/jfrog/charts/issues/1645) +* Use an alternate command for `find` to copy custom certificates +* Added support for circle of trust using `circleOfTrustCertificatesSecret` secret name [GH-1623](https://github.com/jfrog/charts/pull/1623) + +## [107.40.0] - June 16, 2022 +* Added support for PodDisruptionBudget [GH-1618](https://github.com/jfrog/charts/issues/1618) +* From artifactory 7.38.x, joinKey can be retrived from Admin > User Management > Settings in UI +* Allow templating for pod annotations [GH-1634](https://github.com/jfrog/charts/pull/1634) +* Fixed `customPersistentPodVolumeClaim` name to `customPersistentVolumeClaim` +* Added flags to control enable/disable infra services in splitServicesToContainers + +## [107.39.0] - May 31, 2022 +* Fix default `artifactory.async.corePoolSize` [GH-1612](https://github.com/jfrog/charts/issues/1612) +* Added support of nginx annotations +* Reduce startupProbe `initialDelaySeconds` +* Align all liveness and readiness probes failureThreshold to `5` seconds +* Added new flag `unifiedSecretInstallation` to enables single unified secret holding all the artifactory secrets +* Updated router version to `7.38.0` +* Add support for NFS config with directories `haBackupDir` and `haDataDir` +* Fixed - disable jfconnect on oss/jcr/cpp flavours [GH-1630](https://github.com/jfrog/charts/issues/1630) + +## [107.38.0] - May 04, 2022 +* Added support for `global.nodeSelector` to artifactory and nginx pods +* Updated router version to `7.36.1` +* Added support for custom global probes timeout +* Updated frontend container command +* Added topologySpreadConstraints to artifactory and nginx, and add lifecycle hooks to nginx [GH-1596](https://github.com/jfrog/charts/pull/1596) +* Added support of extraEnvironmentVariables for all infra services containers +* Enabled the consumption (jfconnect) flag by default +* Fix jfconnect disabling on non-splitcontainers + +## [107.37.0] - Mar 08, 2022 +* Added support for customPorts in nginx deployment +* Bugfix - Wrong proxy_pass configurations for /artifactory/ in the default artifactory.conf +* Added signedUrlExpirySeconds option to artifactory.persistence.type aws-S3-V3 +* Updated router version to `7.35.0` +* Added useInstanceCredentials,enableSignedUrlRedirect option to google-storage-v2 +* Changed dependency charts repo to `charts.jfrog.io` + +## [107.36.0] - Mar 03, 2022 +* Remove pdn tracker which starts replicator service +* Added silent option for curl probes +* Added readiness health check for the artifactory container for k8s version < 1.20 +* Fix property file migration issue to system.yaml 6.x to 7.x + +## [107.35.0] - Feb 08, 2022 +* Updated router version to `7.32.1` + +## [107.33.0] - Jan 11, 2022 +* Add more user friendly support for anti-affinity +* Pod anti-affinity is now enabled by default (soft rule) +* Readme fixes +* Added support for setting `fsGroupChangePolicy` +* Added nginx customInitContainers, customVolumes, customSidecarContainers [GH-1565](https://github.com/jfrog/charts/pull/1565) +* Updated router version to `7.30.0` + +## [107.32.0] - Dec 22, 2021 +* Updated logger image to `jfrog/ubi-minimal:8.5-204` +* Added default `8091` as `artifactory.tomcat.maintenanceConnector.port` for probes check +* Refactored probes to replace httpGet probes with basic exec + curl +* Refactored `database-creds` secret to create only when database values are passed +* Added new endpoints for probes `/artifactory/api/v1/system/liveness` and `/artifactory/api/v1/system/readiness` +* Enabled `newProbes:true` by default to use these endpoints +* Fix filebeat sidecar spool file permissions +* Updated filebeat sidecar container to `7.16.2` + +## [107.31.0] - Dec 17, 2021 +* Added support for HorizontalPodAutoscaler apiVersion `autoscaling/v2beta2` +* Remove integration service feature flag to make it mandatory service +* Update postgresql tag version to `13.4.0-debian-10-r39` +* Fixed `artifactory.resources` indentation in `migration-artifactory` init container [GH-1562](https://github.com/jfrog/charts/issues/1562) +* Refactored `router.requiredServiceTypes` to support platform chart + +## [107.30.0] - Nov 30, 2021 +* Fixed incorrect permission for filebeat.yaml +* Updated healthcheck (liveness/readiness) api for integration service +* Disable readiness health check for the artifactory container when running in the container split mode +* Ability to start replicator on enabling pdn tracker + +## [107.29.0] - Nov 26, 2021 +* Added integration service container in artifactory +* Add support for Ingress Class Name in Ingress Spec [GH-1516](https://github.com/jfrog/charts/pull/1516) +* Fixed chart values to use curl instead of wget [GH-1529](https://github.com/jfrog/charts/issues/1529) +* Updated nginx config to allow websockets when pipelines is enabled +* Moved router.topology.local.requireqservicetypes from system.yaml to router as environment variable +* Added jfconnect in system.yaml +* Updated artifactory container’s health probes to use artifactory api on rt-split +* Updated initContainerImage to `jfrog/ubi-minimal:8.5-204` +* Updated router version to `7.28.2` +* Set Jfconnect enabled to `false` in the artifactory container when running in the container split mode + +## [107.28.0] - Nov 11, 2021 +* Added default values cpu and memeory in initContainers +* Updated router version to `7.26.0` +* Updated (`rbac.create` and `serviceAccount.create` to false by default) for least privileges +* Fixed incorrect data type for `Values.router.serviceRegistry.insecure` in default values.yaml [GH-1514](https://github.com/jfrog/charts/pull/1514/files) +* **IMPORTANT** +* Changed init-container images from `alpine` to `ubi8/ubi-minimal` +* Added support for AWS License Manager using `.Values.aws.licenseConfigSecretName` + +## [107.27.0] - Oct 6, 2021 +* **Breaking change** +* Aligned probe structure (moved probes variables under config block) +* Added support for new probes(set to false by default) +* Bugfix - Invalid format for `multiPartLimit,multipartElementSize,maxCacheSize` in binarystore.xml [GH-1466](https://github.com/jfrog/charts/issues/1466) +* Added missioncontrol container in artifactory +* Dropped NET_RAW capability for the containers +* Added resources to migration-artifactory init container +* Added resources to all rt split containers +* Updated router version to `7.25.1` +* Added support for Ingress networking.k8s.io/v1/Ingress for k8s >=1.22 [GH-1487](https://github.com/jfrog/charts/pull/1487) +* Added min kubeVersion ">= 1.14.0-0" in chart.yaml +* Update alpine tag version to `3.14.2` +* Update busybox tag version to `1.33.1` +* Artifactory chart support for cluster license + +## [107.26.0] - Aug 23, 2021 +* Added Observability container (only when `splitServicesToContainers` is enabled) +* Support for high availability (when replicaCount > 1) +* Added min kubeVersion ">= 1.12.0-0" in chart.yaml + +## [107.25.0] - Aug 13, 2021 +* Updated readme of chart to point to wiki. Refer [Installing Artifactory](https://www.jfrog.com/confluence/display/JFROG/Installing+Artifactory) +* Added startupProbe and livenessProbe for RT-split containers +* Updated router version to 7.24.1 +* Added security hardening fixes +* Enabled startup probes for k8s >= 1.20.x +* Changed network policy to allow all ingress and egress traffic +* Added Observability changes +* Added support for global.versions.router (only when `splitServicesToContainers` is enabled) + +## [107.24.0] - July 27, 2021 +* Support global and product specific tags at the same time +* Added support for artifactory containers split + +## [107.23.0] - July 8, 2021 +* Bug fix - logger sideCar picks up Wrong File in helm +* Allow filebeat metrics configuration in values.yaml + +## [107.22.0] - July 6, 2021 +* Update alpine tag version to `3.14.0` +* Added `nodePort` support to artifactory-service and nginx-service templates +* Removed redundant `terminationGracePeriodSeconds` in statefulset +* Increased `startupProbe.failureThreshold` time + +## [107.21.3] - July 2, 2021 +* Added ability to change sendreasonphrase value in server.xml via system yaml + +## [107.19.3] - May 20, 2021 +* Fix broken support for startupProbe for k8s < 1.18.x +* Added support for `nameOverride` and `fullnameOverride` in values.yaml + +## [107.18.6] - April 29, 2021 +* Bumping chart version to align with app version +* Add `securityContext` option on nginx container + +## [12.0.0] - April 22, 2021 +* **Breaking change:** +* Increased default postgresql persistence size to `200Gi` +* Update postgresql tag version to `13.2.0-debian-10-r55` +* Update postgresql chart version to `10.3.18` in chart.yaml - [10.x Upgrade Notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1000) +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x/12.x's postgresql.image.tag, previous postgresql.persistence.size and databaseUpgradeReady=true +* **IMPORTANT** +* This chart is only helm v3 compatible. +* Fixed filebeat-configmap naming +* Explicitly set ServiceAccount `automountServiceAccountToken` to 'true' +* Update alpine tag version to `3.13.5` + +## [11.13.2] - April 15, 2021 +* Updated Artifactory version to 7.17.9 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.9) + +## [11.13.1] - April 6, 2021 +* Updated Artifactory version to 7.17.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.6) +* Update alpine tag version to `3.13.4` + +## [11.13.0] - April 5, 2021 +* **IMPORTANT** +* Added `charts.jfrog.io` as default JFrog Helm repository +* Updated Artifactory version to 7.17.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.5) + +## [11.12.2] - Mar 31, 2021 +* Updated Artifactory version to 7.17.4 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.17.4) + +## [11.12.1] - Mar 30, 2021 +* Updated Artifactory version to 7.17.3 +* Add `timeoutSeconds` to all exec probes - Please refer [here](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes) + +## [11.12.0] - Mar 24, 2021 +* Updated Artifactory version to 7.17.2 +* Optimized startupProbe time + +## [11.11.0] - Mar 18, 2021 +* Add support to startupProbe + +## [11.10.0] - Mar 15, 2021 +* Updated Artifactory version to 7.16.3 + +## [11.9.5] - Mar 09, 2021 +* Added HSTS header to nginx conf + +## [11.9.4] - Mar 9, 2021 +* Removed bintray URL references in the chart + +## [11.9.3] - Mar 04, 2021 +* Updated Artifactory version to 7.15.4 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.15.4) + +## [11.9.2] - Mar 04, 2021 +* Fixed creation of nginx-certificate-secret when Nginx is disabled + +## [11.9.1] - Feb 19, 2021 +* Update busybox tag version to `1.32.1` + +## [11.9.0] - Feb 18, 2021 +* Updated Artifactory version to 7.15.3 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.15.3) +* Add option to specify update strategy for Artifactory statefulset + +## [11.8.1] - Feb 11, 2021 +* Exposed "multiPartLimit" and "multipartElementSize" for the Azure Blob Storage Binary Provider + +## [11.8.0] - Feb 08, 2021 +* Updated Artifactory version to 7.12.8 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.12.8) +* Support for custom certificates using secrets +* **Important:** Switched docker images download from `docker.bintray.io` to `releases-docker.jfrog.io` +* Update alpine tag version to `3.13.1` + +## [11.7.8] - Jan 25, 2021 +* Add support for hostAliases + +## [11.7.7] - Jan 11, 2021 +* Fix failures when using creds file for configurating google storage + +## [11.7.6] - Jan 11, 2021 +* Updated Artifactory version to 7.12.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.12.6) + +## [11.7.5] - Jan 07, 2021 +* Added support for optional tracker dedicated ingress `.Values.artifactory.replicator.trackerIngress.enabled` (defaults to false) + +## [11.7.4] - Jan 04, 2021 +* Fixed gid support for statefulset + +## [11.7.3] - Dec 31, 2020 +* Added gid support for statefulset +* Add setSecurityContext flag to allow securityContext block to be removed from artifactory statefulset + +## [11.7.2] - Dec 29, 2020 +* **Important:** Removed `.Values.metrics` and `.Values.fluentd` (Fluentd and Prometheus integrations) +* Add support for creating additional kubernetes resources - [refer here](https://github.com/jfrog/log-analytics-prometheus/blob/master/artifactory-values.yaml) +* Updated Artifactory version to 7.12.5 + +## [11.7.1] - Dec 21, 2020 +* Updated Artifactory version to 7.12.3 + +## [11.7.0] - Dec 18, 2020 +* Updated Artifactory version to 7.12.2 +* Added `.Values.artifactory.openMetrics.enabled` + +## [11.6.1] - Dec 11, 2020 +* Added configurable `.Values.global.versions.artifactory` in values.yaml + +## [11.6.0] - Dec 10, 2020 +* Update postgresql tag version to `12.5.0-debian-10-r25` +* Fixed `artifactory.persistence.googleStorage.endpoint` from `storage.googleapis.com` to `commondatastorage.googleapis.com` +* Updated chart maintainers email + +## [11.5.5] - Dec 4, 2020 +* **Important:** Renamed `.Values.systemYaml` to `.Values.systemYamlOverride` + +## [11.5.4] - Dec 1, 2020 +* Improve error message returned when attempting helm upgrade command + +## [11.5.3] - Nov 30, 2020 +* Updated Artifactory version to 7.11.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.11) + +## [11.5.2] - Nov 23, 2020 +* Updated Artifactory version to 7.11.2 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.11) +* Updated port namings on services and pods to allow for istio protocol discovery +* Change semverCompare checks to support hosted Kubernetes +* Add flag to disable creation of ServiceMonitor when enabling prometheus metrics +* Prevent the PostHook command to be executed if the user did not specify a command in the values file +* Fix issue with tls file generation when nginx.https.enabled is false + +## [11.5.1] - Nov 19, 2020 +* Updated Artifactory version to 7.11.2 +* Bugfix - access.config.import.xml override Access Federation configurations + +## [11.5.0] - Nov 17, 2020 +* Updated Artifactory version to 7.11.1 +* Update alpine tag version to `3.12.1` + +## [11.4.6] - Nov 10, 2020 +* Pass system.yaml via external secret for advanced usecases +* Added support for custom ingress +* Bugfix - stateful set not picking up changes to database secrets + +## [11.4.5] - Nov 9, 2020 +* Updated Artifactory version to 7.10.6 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.6) + +## [11.4.4] - Nov 2, 2020 +* Add enablePathStyleAccess property for aws-s3-v3 binary provider template + +## [11.4.3] - Nov 2, 2020 +* Updated Artifactory version to 7.10.5 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.5) + +## [11.4.2] - Oct 22, 2020 +* Chown bug fix where Linux capability cannot chown all files causing log line warnings +* Fix Frontend timeout linting issue + +## [11.4.1] - Oct 20, 2020 +* Add flag to disable prepare-custom-persistent-volume init container + +## [11.4.0] - Oct 19, 2020 +* Updated Artifactory version to 7.10.2 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.10.2) + +## [11.3.2] - Oct 15, 2020 +* Add support to specify priorityClassName for nginx deployment + +## [11.3.1] - Oct 9, 2020 +* Add support for customInitContainersBegin + +## [11.3.0] - Oct 7, 2020 +* Updated Artifactory version to 7.9.1 +* **Breaking change:** Fix `storageClass` to correct `storageClassName` in values.yaml + +## [11.2.0] - Oct 5, 2020 +* Expose Prometheus metrics via a ServiceMonitor +* Parse log files for metric data with Fluentd + +## [11.1.0] - Sep 30, 2020 +* Updated Artifactory version to 7.9.0 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.9) +* Added support for resources in init container + +## [11.0.11] - Sep 25, 2020 +* Update to use linux capability CAP_CHOWN instead of root base init container to avoid any use of root containers to pass Redhat security requirements + +## [11.0.10] - Sep 28, 2020 +* Setting chart coordinates in migitation yaml + +## [11.0.9] - Sep 25, 2020 +* Update filebeat version to `7.9.2` + +## [11.0.8] - Sep 24, 2020 +* Fixed broken issue - when setting `waitForDatabase: false` container startup still waits for DB + +## [11.0.7] - Sep 22, 2020 +* Readme updates + +## [11.0.6] - Sep 22, 2020 +* Fix lint issue in migitation yaml + +## [11.0.5] - Sep 22, 2020 +* Fix broken migitation yaml + +## [11.0.4] - Sep 21, 2020 +* Added mitigation yaml for Artifactory - [More info](https://github.com/jfrog/chartcenter/blob/master/docs/securitymitigationspec.md) + +## [11.0.3] - Sep 17, 2020 +* Added configurable session(UI) timeout in frontend microservice + +## [11.0.2] - Sep 17, 2020 +* Added proper required text to be shown while postgres upgrades + +## [11.0.1] - Sep 14, 2020 +* Updated Artifactory version to 7.7.8 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.7.8) + +## [11.0.0] - Sep 2, 2020 +* **Breaking change:** Changed `imagePullSecrets`values from string to list. +* **Breaking change:** Added `image.registry` and changed `image.version` to `image.tag` for docker images +* Added support for global values +* Updated maintainers in chart.yaml +* Update postgresql tag version to `12.3.0-debian-10-r71` +* Update postgresql chart version to `9.3.4` in requirements.yaml - [9.x Upgrade Notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#900) +* **IMPORTANT** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass previous 9.x/10.x's postgresql.image.tag and databaseUpgradeReady=true + +## [10.1.0] - Aug 13, 2020 +* Updated Artifactory version to 7.7.3 - [Release Notes](https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.7) + +## [10.0.15] - Aug 10, 2020 +* Added enableSignedUrlRedirect for persistent storage type aws-s3-v3. + +## [10.0.14] - Jul 31, 2020 +* Update the README section on Nginx SSL termination to reflect the actual YAML structure. + +## [10.0.13] - Jul 30, 2020 +* Added condition to disable the migration scripts. + +## [10.0.12] - Jul 28, 2020 +* Document Artifactory node affinity. + +## [10.0.11] - Jul 28, 2020 +* Added maxConnections for persistent storage type aws-s3-v3. + +## [10.0.10] - Jul 28, 2020 +* Bugfix / support for userPluginSecrets with Artifactory 7 + +## [10.0.9] - Jul 27, 2020 +* Add tpl to external database secrets +* Modified `scheme` to `artifactory.scheme` + +## [10.0.8] - Jul 23, 2020 +* Added condition to disable the migration init container. + +## [10.0.7] - Jul 21, 2020 +* Updated Artifactory Chart to add node and primary labels to pods and service objects. + +## [10.0.6] - Jul 20, 2020 +* Support custom CA and certificates + +## [10.0.5] - Jul 13, 2020 +* Updated Artifactory version to 7.6.3 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.6.3 +* Fixed Mysql database jar path in `preStartCommand` in README + +## [10.0.4] - Jul 10, 2020 +* Move some postgresql values to where they should be according to the subchart + +## [10.0.3] - Jul 8, 2020 +* Set Artifactory access client connections to the same value as the access threads + +## [10.0.2] - Jul 6, 2020 +* Updated Artifactory version to 7.6.2 +* **IMPORTANT** +* Added ChartCenter Helm repository in README + +## [10.0.1] - Jul 01, 2020 +* Add dedicated ingress object for Replicator service when enabled + +## [10.0.0] - Jun 30, 2020 +* Update postgresql tag version to `10.13.0-debian-10-r38` +* Update alpine tag version to `3.12` +* Update busybox tag version to `1.31.1` +* **IMPORTANT** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), you need to pass postgresql.image.tag=9.6.18-debian-10-r7 and databaseUpgradeReady=true + +## [9.6.0] - Jun 29, 2020 +* Updated Artifactory version to 7.6.1 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.6.1 +* Add tpl for external database secrets + +## [9.5.5] - Jun 25, 2020 +* Stop loading the Nginx stream module because it is now a core module + +## [9.5.4] - Jun 25, 2020 +* Notes.txt update - add --namespace parameter + +## [9.5.3] - Jun 11, 2020 +* Support list of custom secrets + +## [9.5.2] - Jun 12, 2020 +* Updated Artifactory version to 7.5.7 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.5.7 + +## [9.5.1] - Jun 8, 2020 +* Readme update - configuring Artifactory with oracledb + +## [9.5.0] - Jun 1, 2020 +* Updated Artifactory version to 7.5.5 - https://www.jfrog.com/confluence/display/JFROG/Artifactory+Release+Notes#ArtifactoryReleaseNotes-Artifactory7.5 +* Fixes bootstrap configMap permission issue +* Update postgresql tag version to `9.6.18-debian-10-r7` + +## [9.4.9] - May 27, 2020 +* Added Tomcat maxThreads & acceptCount + +## [9.4.8] - May 25, 2020 +* Fixed postgresql README `image` Parameters + +## [9.4.7] - May 24, 2020 +* Fixed typo in README regarding migration timeout + +## [9.4.6] - May 19, 2020 +* Added metadata maxOpenConnections + +## [9.4.5] - May 07, 2020 +* Fix `installerInfo` string format + +## [9.4.4] - Apr 27, 2020 +* Updated Artifactory version to 7.4.3 + +## [9.4.3] - Apr 26, 2020 +* Change order of the customInitContainers to run before the "migration-artifactory" initContainer. + +## [9.4.2] - Apr 24, 2020 +* Fix `artifactory.persistence.awsS3V3.useInstanceCredentials` incorrect conditional logic +* Bump postgresql tag version to `9.6.17-debian-10-r72` in values.yaml + +## [9.4.1] - Apr 16, 2020 +* Custom volumes in migration init container. + +## [9.4.0] - Apr 14, 2020 +* Updated Artifactory version to 7.4.1 + +## [9.3.1] - April 13, 2020 +* Update README with helm v3 commands + +## [9.3.0] - April 10, 2020 +* Use dependency charts from `https://charts.bitnami.com/bitnami` +* Bump postgresql chart version to `8.7.3` in requirements.yaml +* Bump postgresql tag version to `9.6.17-debian-10-r21` in values.yaml + +## [9.2.9] - Apr 8, 2020 +* Added recommended ingress annotation to avoid 413 errors + +## [9.2.8] - Apr 8, 2020 +* Moved migration scripts under `files` directory +* Support preStartCommand in migration Init container as `artifactory.migration.preStartCommand` + +## [9.2.7] - Apr 6, 2020 +* Fix cache size (should be 5gb instead of 50gb since volume claim is only 20gb). + +## [9.2.6] - Apr 1, 2020 +* Support masterKey and joinKey as secrets + +## [9.2.5] - Apr 1, 2020 +* Fix readme use to `-hex 32` instead of `-hex 16` + +## [9.2.4] - Mar 31, 2020 +* Change the way the artifactory `command:` is set so it will properly pass a SIGTERM to java + +## [9.2.3] - Mar 29, 2020 +* Add Nginx log options: stderr as logfile and log level + +## [9.2.2] - Mar 30, 2020 +* Use the same defaulting mechanism used for the artifactory version used elsewhere in the chart + +## [9.2.1] - Mar 29, 2020 +* Fix loggers sidecars configurations to support new file system layout and new log names + +## [9.2.0] - Mar 29, 2020 +* Fix broken admin user bootstrap configuration +* **Breaking change:** renamed `artifactory.accessAdmin` to `artifactory.admin` + +## [9.1.5] - Mar 26, 2020 +* Fix volumeClaimTemplate issue + +## [9.1.4] - Mar 25, 2020 +* Fix volume name used by filebeat container + +## [9.1.3] - Mar 24, 2020 +* Use `postgresqlExtendedConf` for setting custom PostgreSQL configuration (instead of `postgresqlConfiguration`) + +## [9.1.2] - Mar 22, 2020 +* Support for SSL offload in Nginx service(LoadBalancer) layer. Introduced `nginx.service.ssloffload` field with boolean type. + +## [9.1.1] - Mar 23, 2020 +* Moved installer info to values.yaml so it is fully customizable + +## [9.1.0] - Mar 23, 2020 +* Updated Artifactory version to 7.3.2 + +## [9.0.29] - Mar 20, 2020 +* Add support for masterKey trim during 6.x to 7.x migration if 6.x masterKey is 32 hex (64 characters) + +## [9.0.28] - Mar 18, 2020 +* Increased Nginx proxy_buffers size + +## [9.0.27] - Mar 17, 2020 +* Changed all single quotes to double quotes in values files +* useInstanceCredentials variable was declared in S3 settings but not used in chart. Now it is being used. + +## [9.0.26] - Mar 17, 2020 +* Fix rendering of Service Account annotations + +## [9.0.25] - Mar 16, 2020 +* Update Artifactory readme with extra ingress annotations needed for Artifactory to be set as SSO provider + +## [9.0.24] - Mar 16, 2020 +* Add Unsupported message from 6.18 to 7.2.x (migration) + +## [9.0.23] - Mar 12, 2020 +* Fix README.md rendering issue + +## [9.0.22] - Mar 11, 2020 +* Upgrade Docs update + +## [9.0.21] - Mar 11, 2020 +* Unified charts public release + +## [9.0.20] - Mar 6, 2020 +* Fix path to `/artifactory_bootstrap` +* Add support for controlling the name of the ingress and allow to set more than one cname + +## [9.0.19] - Mar 4, 2020 +* Add support for disabling `consoleLog` in `system.yaml` file + +## [9.0.18] - Feb 28, 2020 +* Add support to process `valueFrom` for extraEnvironmentVariables + +## [9.0.17] - Feb 26, 2020 +* Fix join key secret naming + +## [9.0.16] - Feb 26, 2020 +* Store join key to secret + +## [9.0.15] - Feb 26, 2020 +* Updated Artifactory version to 7.2.1 + +## [9.0.10] - Feb 07, 2020 +* Remove protection flag `databaseUpgradeReady` which was added to check internal postgres upgrade + +## [9.0.0] - Feb 07, 2020 +* Updated Artifactory version to 7.0.0 + +## [8.4.8] - Feb 13, 2020 +* Add support for SSH authentication to Artifactory + +## [8.4.7] - Feb 11, 2020 +* Change Artifactory service port name to be hard-coded to `http` instead of using `{{ .Release.Name }}` + +## [8.4.6] - Feb 9, 2020 +* Add support for `tpl` in the `postStartCommand` + +## [8.4.5] - Feb 4, 2020 +* Support customisable Nginx kind + +## [8.4.4] - Feb 2, 2020 +* Add a comment stating that it is recommended to use an external PostgreSQL with a static password for production installations + +## [8.4.3] - Jan 30, 2020 +* Add the option to configure resources for the logger containers + +## [8.4.2] - Jan 26, 2020 +* Improve `database.user` and `database.password` logic in order to support more use cases and make the configuration less repetitive + +## [8.4.1] - Jan 19, 2020 +* Fix replicator port config in nginx replicator configmap + +## [8.4.0] - Jan 19, 2020 +* Updated Artifactory version to 6.17.0 + +## [8.3.6] - Jan 16, 2020 +* Added example for external nginx-ingress + +## [8.3.5] - Dec 30, 2019 +* Fix for nginx probes failing when launched with http disabled + +## [8.3.4] - Dec 24, 2019 +* Better support for custom `artifactory.internalPort` + +## [8.3.3] - Dec 23, 2019 +* Mark empty map values with `{}` + +## [8.3.2] - Dec 16, 2019 +* Fix for toggling nginx service ports + +## [8.3.1] - Dec 12, 2019 +* Add support for toggling nginx service ports + +## [8.3.0] - Dec 1, 2019 +* Updated Artifactory version to 6.16.0 + +## [8.2.6] - Nov 28, 2019 +* Add support for using existing PriorityClass + +## [8.2.5] - Nov 27, 2019 +* Add support for PriorityClass + +## [8.2.4] - Nov 21, 2019 +* Add an option to use a file system cache-fs with the file-system binarystore template + +## [8.2.3] - Nov 20, 2019 +* Update Artifactory Readme + +## [8.2.2] - Nov 20, 2019 +* Update Artfactory logo + +## [8.2.1] - Nov 18, 2019 +* Add the option to provide service account annotations (in order to support stuff like https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html) + +## [8.2.0] - Nov 18, 2019 +* Updated Artifactory version to 6.15.0 + +## [8.1.11] - Nov 17, 2019 +* Do not provide a default master key. Allow it to be auto generated by Artifactory on first startup + +## [8.1.10] - Nov 17, 2019 +* Fix creation of double slash in nginx artifactory configuration + +## [8.1.9] - Nov 14, 2019 +* Set explicit `postgresql.postgresqlPassword=""` to avoid helm v3 error + +## [8.1.8] - Nov 12, 2019 +* Updated Artifactory version to 6.14.1 + +## [8.1.7] - Nov 9, 2019 +* Additional documentation for masterKey + +## [8.1.6] - Nov 10, 2019 +* Update PostgreSQL chart version to 7.0.1 +* Use formal PostgreSQL configuration format + +## [8.1.5] - Nov 8, 2019 +* Add support `artifactory.service.loadBalancerSourceRanges` for whitelisting when setting `artifactory.service.type=LoadBalancer` + +## [8.1.4] - Nov 6, 2019 +* Add support for any type of environment variable by using `extraEnvironmentVariables` as-is + +## [8.1.3] - Nov 6, 2019 +* Add nodeselector support for Postgresql + +## [8.1.2] - Nov 5, 2019 +* Add support for the aws-s3-v3 filestore, which adds support for pod IAM roles + +## [8.1.1] - Nov 4, 2019 +* When using `copyOnEveryStartup`, make sure that the target base directories are created before copying the files + +## [8.1.0] - Nov 3, 2019 +* Updated Artifactory version to 6.14.0 + +## [8.0.1] - Nov 3, 2019 +* Make sure the artifactory pod exits when one of the pre-start stages fail + +## [8.0.0] - Oct 27, 2019 +**IMPORTANT - BREAKING CHANGES!**
+**DOWNTIME MIGHT BE REQUIRED FOR AN UPGRADE!** +* If this is a new deployment or you already use an external database (`postgresql.enabled=false`), these changes **do not affect you**! +* If this is an upgrade and you are using the default PostgreSQL (`postgresql.enabled=true`), must use the upgrade instructions in [UPGRADE_NOTES.md](UPGRADE_NOTES.md)! +* PostgreSQL sub chart was upgraded to version `6.5.x`. This version is **not backward compatible** with the old version (`0.9.5`)! +* Note the following **PostgreSQL** Helm chart changes + * The chart configuration has changed! See [values.yaml](values.yaml) for the new keys used + * **PostgreSQL** is deployed as a StatefulSet + * See [PostgreSQL helm chart](https://hub.helm.sh/charts/stable/postgresql) for all available configurations + +## [7.18.3] - Oct 24, 2019 +* Change the preStartCommand to support templating + +## [7.18.2] - Oct 21, 2019 +* Add support for setting `artifactory.labels` +* Add support for setting `nginx.labels` + +## [7.18.1] - Oct 10, 2019 +* Updated Artifactory version to 6.13.1 + +## [7.18.0] - Oct 7, 2019 +* Updated Artifactory version to 6.13.0 + +## [7.17.5] - Sep 24, 2019 +* Option to skip wait-for-db init container with '--set waitForDatabase=false' + +## [7.17.4] - Sep 11, 2019 +* Updated Artifactory version to 6.12.2 + +## [7.17.3] - Sep 9, 2019 +* Updated Artifactory version to 6.12.1 + +## [7.17.2] - Aug 22, 2019 +* Fix the nginx server_name directive used with ingress.hosts + +## [7.17.1] - Aug 21, 2019 +* Enable the Artifactory container's liveness and readiness probes + +## [7.17.0] - Aug 21, 2019 +* Updated Artifactory version to 6.12.0 + +## [7.16.11] - Aug 14, 2019 +* Updated Artifactory version to 6.11.6 + +## [7.16.10] - Aug 11, 2019 +* Fix Ingress routing and add an example + +## [7.16.9] - Aug 5, 2019 +* Do not mount `access/etc/bootstrap.creds` unless user specifies a custom password or secret (Access already generates a random password if not provided one) +* If custom `bootstrap.creds` is provided (using keys or custom secret), prepare it with an init container so the temp file does not persist + +## [7.16.8] - Aug 4, 2019 +* Improve binarystore config + 1. Convert to a secret + 2. Move config to values.yaml + 3. Support an external secret + +## [7.16.7] - Jul 29, 2019 +* Don't create the nginx configmaps when nginx.enabled is false + +## [7.16.6] - Jul 24, 2019 +* Simplify nginx setup and shorten initial wait for probes + +## [7.16.5] - Jul 22, 2019 +* Change Ingress API to be compatible with recent kubernetes versions + +## [7.16.4] - Jul 22, 2019 +* Updated Artifactory version to 6.11.3 + +## [7.16.3] - Jul 11, 2019 +* Add ingress.hosts to the Nginx server_name directive when ingress is enabled to help with Docker repository sub domain configuration + +## [7.16.2] - Jul 3, 2019 +* Fix values key in reverse proxy example + +## [7.16.1] - Jul 1, 2019 +* Updated Artifactory version to 6.11.1 + +## [7.16.0] - Jun 27, 2019 +* Update Artifactory version to 6.11 and add restart to Artifactory when bootstrap.creds file has been modified + +## [7.15.8] - Jun 27, 2019 +* Add the option for changing nginx config using values.yaml and remove outdated reverse proxy documentation + +## [7.15.6] - Jun 24, 2019 +* Update chart maintainers + +## [7.15.5] - Jun 24, 2019 +* Change Nginx to point to the artifactory externalPort + +## [7.15.4] - Jun 23, 2019 +* Add the option to provide an IP for the access-admin endpoints + +## [7.15.3] - Jun 23, 2019 +* Add values files for small, medium and large installations + +## [7.15.2] - Jun 20, 2019 +* Add missing terminationGracePeriodSeconds to values.yaml + +## [7.15.1] - Jun 19, 2019 +* Updated Artifactory version to 6.10.4 + +## [7.15.0] - Jun 17, 2019 +* Use configmaps for nginx configuration and remove nginx postStart command + +## [7.14.8] - Jun 18, 2019 +* Add the option to provide additional ingress rules + +## [7.14.7] - Jun 14, 2019 +* Updated readme with improved external database setup example + +## [7.14.6] - Jun 11, 2019 +* Updated Artifactory version to 6.10.3 +* Updated installer-info template + +## [7.14.5] - Jun 6, 2019 +* Updated Google Cloud Storage API URL and https settings + +## [7.14.4] - Jun 5, 2019 +* Delete the db.properties file on Artifactory startup + +## [7.14.3] - Jun 3, 2019 +* Updated Artifactory version to 6.10.2 + +## [7.14.2] - May 21, 2019 +* Updated Artifactory version to 6.10.1 + +## [7.14.1] - May 19, 2019 +* Fix missing logger image tag + +## [7.14.0] - May 7, 2019 +* Updated Artifactory version to 6.10.0 + +## [7.13.21] - May 5, 2019 +* Add support for setting `artifactory.async.corePoolSize` + +## [7.13.20] - May 2, 2019 +* Remove unused property `artifactory.releasebundle.feature.enabled` + +## [7.13.19] - May 1, 2019 +* Fix indentation issue with the replicator system property + +## [7.13.18] - Apr 30, 2019 +* Add support for JMX monitoring + +## [7.13.17] - Apr 25, 2019 +* Added support for `cacheProviderDir` + +## [7.13.16] - Apr 18, 2019 +* Changing API StatefulSet version to `v1` and permission fix for custom `artifactory.conf` for Nginx + +## [7.13.15] - Apr 16, 2019 +* Updated documentation for Reverse Proxy Configuration + +## [7.13.14] - Apr 15, 2019 +* Added support for `customVolumeMounts` + +## [7.13.13] - Aprl 12, 2019 +* Added support for `bucketExists` flag for googleStorage + +## [7.13.12] - Apr 11, 2019 +* Replace `curl` examples with `wget` due to the new base image + +## [7.13.11] - Aprl 07, 2019 +* Add support for providing the Artifactory license as a parameter + +## [7.13.10] - Apr 10, 2019 +* Updated Artifactory version to 6.9.1 + +## [7.13.9] - Aprl 04, 2019 +* Add support for templated extraEnvironmentVariables + +## [7.13.8] - Aprl 07, 2019 +* Change network policy API group + +## [7.13.7] - Aprl 04, 2019 +* Bugfix for userPluginSecrets + +## [7.13.6] - Apr 4, 2019 +* Add information about upgrading Artifactory with auto-generated postgres password + +## [7.13.5] - Aprl 03, 2019 +* Added installer info + +## [7.13.4] - Aprl 03, 2019 +* Allow secret names for user plugins to contain template language + +## [7.13.3] - Apr 02, 2019 +* Allow NetworkPolicy configurations (defaults to allow all) + +## [7.13.2] - Aprl 01, 2019 +* Add support for user plugin secret + +## [7.13.1] - Mar 27, 2019 +* Add the option to copy a list of files to ARTIFACTORY_HOME on startup + +## [7.13.0] - Mar 26, 2019 +* Updated Artifactory version to 6.9.0 + +## [7.12.18] - Mar 25, 2019 +* Add CI tests for persistence, ingress support and nginx + +## [7.12.17] - Mar 22, 2019 +* Add the option to change the default access-admin password + +## [7.12.16] - Mar 22, 2019 +* Added support for `.Probe.path` to customise the paths used for health probes + +## [7.12.15] - Mar 21, 2019 +* Added support for `artifactory.customSidecarContainers` to create custom sidecar containers +* Added support for `artifactory.customVolumes` to create custom volumes + +## [7.12.14] - Mar 21, 2019 +* Make ingress path configurable + +## [7.12.13] - Mar 19, 2019 +* Move the copy of bootstrap config from postStart to preStart + +## [7.12.12] - Mar 19, 2019 +* Fix existingClaim example + +## [7.12.11] - Mar 18, 2019 +* Add information about nginx persistence + +## [7.12.10] - Mar 15, 2019 +* Wait for nginx configuration file before using it + +## [7.12.9] - Mar 15, 2019 +* Revert securityContext changes since they were causing issues + +## [7.12.8] - Mar 15, 2019 +* Fix issue #247 (init container failing to run) + +## [7.12.7] - Mar 14, 2019 +* Updated Artifactory version to 6.8.7 +* Add support for Artifactory-CE for C++ + +## [7.12.6] - Mar 13, 2019 +* Move securityContext to container level + +## [7.12.5] - Mar 11, 2019 +* Updated Artifactory version to 6.8.6 + +## [7.12.4] - Mar 8, 2019 +* Fix existingClaim option + +## [7.12.3] - Mar 5, 2019 +* Updated Artifactory version to 6.8.4 + +## [7.12.2] - Mar 4, 2019 +* Add support for catalina logs sidecars + +## [7.12.1] - Feb 27, 2019 +* Updated Artifactory version to 6.8.3 + +## [7.12.0] - Feb 25, 2019 +* Add nginx support for tail sidecars + +## [7.11.1] - Feb 20, 2019 +* Added support for enterprise storage + +## [7.10.2] - Feb 19, 2019 +* Updated Artifactory version to 6.8.2 + +## [7.10.1] - Feb 17, 2019 +* Updated Artifactory version to 6.8.1 +* Add example of `SERVER_XML_EXTRA_CONNECTOR` usage + +## [7.10.0] - Feb 15, 2019 +* Updated Artifactory version to 6.8.0 + +## [7.9.6] - Feb 13, 2019 +* Updated Artifactory version to 6.7.3 + +## [7.9.5] - Feb 12, 2019 +* Add support for tail sidecars to view logs from k8s api + +## [7.9.4] - Feb 6, 2019 +* Fix support for customizing statefulset `terminationGracePeriodSeconds` + +## [7.9.3] - Feb 5, 2019 +* Add instructions on how to deploy Artifactory with embedded Derby database + +## [7.9.2] - Feb 5, 2019 +* Add support for customizing statefulset `terminationGracePeriodSeconds` + +## [7.9.1] - Feb 3, 2019 +* Updated Artifactory version to 6.7.2 + +## [7.9.0] - Jan 23, 2019 +* Updated Artifactory version to 6.7.0 + +## [7.8.9] - Jan 22, 2019 +* Added support for `artifactory.customInitContainers` to create custom init containers + +## [7.8.8] - Jan 17, 2019 +* Added support of values ingress.labels + +## [7.8.7] - Jan 16, 2019 +* Mount replicator.yaml (config) directly to /replicator_extra_conf + +## [7.8.6] - Jan 13, 2019 +* Fix documentation about nginx group id + +## [7.8.5] - Jan 13, 2019 +* Updated Artifactory version to 6.6.5 + +## [7.8.4] - Jan 8, 2019 +* Make artifactory.replicator.publicUrl required when the replicator is enabled + +## [7.8.3] - Jan 1, 2019 +* Updated Artifactory version to 6.6.3 +* Add support for `artifactory.extraEnvironmentVariables` to pass more environment variables to Artifactory + +## [7.8.2] - Dec 28, 2018 +* Fix location `replicator.yaml` is copied to + +## [7.8.1] - Dec 27, 2018 +* Updated Artifactory version to 6.6.1 + +## [7.8.0] - Dec 20, 2018 +* Updated Artifactory version to 6.6.0 + +## [7.7.13] - Dec 17, 2018 +* Updated Artifactory version to 6.5.13 + +## [7.7.12] - Dec 12, 2018 +* Fix documentation about Artifactory license setup using secret + +## [7.7.11] - Dec 10, 2018 +* Fix issue when using existing claim + +## [7.7.10] - Dec 5, 2018 +* Remove Distribution certificates creation. + +## [7.7.9] - Nov 30, 2018 +* Updated Artifactory version to 6.5.9 + +## [7.7.8] - Nov 29, 2018 +* Updated postgresql version to 9.6.11 + +## [7.7.7] - Nov 27, 2018 +* Updated Artifactory version to 6.5.8 + +## [7.7.6] - Nov 19, 2018 +* Added support for configMap to use custom Reverse Proxy Configuration with Nginx + +## [7.7.5] - Nov 14, 2018 +* Fix location of `nodeSelector`, `affinity` and `tolerations` + +## [7.7.4] - Nov 14, 2018 +* Updated Artifactory version to 6.5.3 + +## [7.7.3] - Nov 12, 2018 +* Support artifactory.preStartCommand for running command before entrypoint starts + +## [7.7.2] - Nov 7, 2018 +* Support database.url parameter (DB_URL) + +## [7.7.1] - Oct 29, 2018 +* Change probes port to 8040 (so they will not be blocked when all tomcat threads on 8081 are exhausted) + +## [7.7.0] - Oct 28, 2018 +* Update postgresql chart to version 0.9.5 to be able and use `postgresConfig` options + +## [7.6.8] - Oct 23, 2018 +* Fix providing external secret for database credentials + +## [7.6.7] - Oct 23, 2018 +* Allow user to configure externalTrafficPolicy for Loadbalancer + +## [7.6.6] - Oct 22, 2018 +* Updated ingress annotation support (with examples) to support docker registry v2 + +## [7.6.5] - Oct 21, 2018 +* Updated Artifactory version to 6.5.2 + +## [7.6.4] - Oct 19, 2018 +* Allow providing pre-existing secret containing master key +* Allow arbitrary annotations on primary and member node pods +* Enforce size limits when using local storage with `emptyDir` +* Allow providing pre-existing secrets containing external database credentials + +## [7.6.3] - Oct 18, 2018 +* Updated Artifactory version to 6.5.1 + +## [7.6.2] - Oct 17, 2018 +* Add Apache 2.0 license + +## [7.6.1] - Oct 11, 2018 +* Supports master-key in the secrets and stateful-set +* Allows ingress default `backend` to be enabled or disabled (defaults to enabled) + +## [7.6.0] - Oct 11, 2018 +* Updated Artifactory version to 6.5.0 + +## [7.5.4] - Oct 9, 2018 +* Quote ingress hosts to support wildcard names + +## [7.5.3] - Oct 4, 2018 +* Add PostgreSQL resources template + +## [7.5.2] - Oct 2, 2018 +* Add `helm repo add jfrog https://charts.jfrog.io` to README + +## [7.5.1] - Oct 2, 2018 +* Set Artifactory to 6.4.1 + +## [7.5.0] - Sep 27, 2018 +* Set Artifactory to 6.4.0 + +## [7.4.3] - Sep 26, 2018 +* Add ci/test-values.yaml + +## [7.4.2] - Sep 2, 2018 +* Updated Artifactory version to 6.3.2 +* Removed unused PVC + +## [7.4.0] - Aug 22, 2018 +* Added support to run as non root +* Updated Artifactory version to 6.2.0 + +## [7.3.0] - Aug 22, 2018 +* Enabled RBAC Support +* Added support for PostStartCommand (To download Database JDBC connector) +* Increased postgresql max_connections +* Added support for `nginx.conf` ConfigMap +* Updated Artifactory version to 6.1.0 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.lock b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.lock new file mode 100644 index 0000000000..8064c323b5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.jfrog.io/ + version: 10.3.18 +digest: sha256:404ce007353baaf92a6c5f24b249d5b336c232e5fd2c29f8a0e4d0095a09fd53 +generated: "2022-03-08T08:53:16.293311+05:30" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.yaml new file mode 100644 index 0000000000..87c63a4ab1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: 7.90.9 +dependencies: +- condition: postgresql.enabled + name: postgresql + repository: https://charts.jfrog.io/ + version: 10.3.18 +description: Universal Repository Manager supporting all major packaging formats, + build tools and CI servers. +home: https://www.jfrog.com/artifactory/ +icon: https://raw.githubusercontent.com/jfrog/charts/master/stable/artifactory/logo/artifactory-logo.png +keywords: +- artifactory +- jfrog +- devops +kubeVersion: '>= 1.19.0-0' +maintainers: +- email: installers@jfrog.com + name: Chart Maintainers at JFrog +name: artifactory +sources: +- https://github.com/jfrog/charts +type: application +version: 107.90.9 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/LICENSE b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/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/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/README.md new file mode 100644 index 0000000000..da3304ee58 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/README.md @@ -0,0 +1,59 @@ +# JFrog Artifactory Helm Chart + +**IMPORTANT!** Our Helm Chart docs have moved to our main documentation site. Below you will find the basic instructions for installing, uninstalling, and deleting Artifactory. For all other information, refer to [Installing Artifactory](https://www.jfrog.com/confluence/display/JFROG/Installing+Artifactory#InstallingArtifactory-HelmInstallation). + +## Prerequisites +* Kubernetes 1.19+ +* Artifactory Pro trial license [get one from here](https://www.jfrog.com/artifactory/free-trial/) + +## Chart Details +This chart will do the following: + +* Deploy Artifactory-Pro/Artifactory-Edge (or OSS/CE if custom image is set) +* Deploy a PostgreSQL database using the stable/postgresql chart (can be changed) **NOTE:** For production grade installations it is recommended to use an external PostgreSQL. +* Deploy an optional Nginx server +* Optionally expose Artifactory with Ingress [Ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/) + +## Installing the Chart + +### Add JFrog Helm repository + +Before installing JFrog helm charts, you need to add the [JFrog helm repository](https://charts.jfrog.io) to your helm client + +```bash +helm repo add jfrog https://charts.jfrog.io +helm repo update +``` + +### Install Chart +To install the chart with the release name `artifactory`: +```bash +helm upgrade --install artifactory jfrog/artifactory --namespace artifactory --create-namespace +``` + +### Apply Sizing configurations to the Chart +To apply the chart with recommended sizing configurations : +For small configurations : +```bash +helm upgrade --install artifactory jfrog/artifactory -f sizing/artifactory-small-extra-config.yaml -f sizing/artifactory-small.yaml --namespace artifactory --create-namespace +``` + +## Uninstalling Artifactory + +Uninstall is supported only on Helm v3 and on. + +Uninstall Artifactory using the following command. + +```bash +helm uninstall artifactory && sleep 90 && kubectl delete pvc -l app=artifactory +``` + +## Deleting Artifactory + +**IMPORTANT:** Deleting Artifactory will also delete your data volumes and you will lose all of your data. You must back up all this information before deletion. You do not need to uninstall Artifactory before deleting it. + +To delete Artifactory use the following command. + +```bash +helm delete artifactory --namespace artifactory +``` diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/.helmignore b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/.helmignore new file mode 100644 index 0000000000..f0c1319444 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.lock b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.lock new file mode 100644 index 0000000000..3687f52df5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.4.2 +digest: sha256:dce0349883107e3ff103f4f17d3af4ad1ea3c7993551b1c28865867d3e53d37c +generated: "2021-03-30T09:13:28.360322819Z" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.yaml new file mode 100644 index 0000000000..4b197b2071 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/Chart.yaml @@ -0,0 +1,29 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 11.11.0 +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.x.x +description: Chart for PostgreSQL, an object-relational database management system + (ORDBMS) with an emphasis on extensibility and on standards-compliance. +home: https://github.com/bitnami/charts/tree/master/bitnami/postgresql +icon: https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-220x234.png +keywords: +- postgresql +- postgres +- database +- sql +- replication +- cluster +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: cedric@desaintmartin.fr + name: desaintmartin +name: postgresql +sources: +- https://github.com/bitnami/bitnami-docker-postgresql +- https://www.postgresql.org/ +version: 10.3.18 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/README.md new file mode 100644 index 0000000000..63d3605bb8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/README.md @@ -0,0 +1,770 @@ +# PostgreSQL + +[PostgreSQL](https://www.postgresql.org/) is an object-relational database management system (ORDBMS) with an emphasis on extensibility and on standards-compliance. + +For HA, please see [this repo](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha) + +## TL;DR + +```console +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/postgresql +``` + +## Introduction + +This chart bootstraps a [PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This chart has been tested to work with NGINX Ingress, cert-manager, fluentd and Prometheus on top of the [BKPR](https://kubeprod.io/). + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.1.0 +- PV provisioner support in the underlying infrastructure + +## Installing the Chart +To install the chart with the release name `my-release`: + +```console +$ helm install my-release bitnami/postgresql +``` + +The command deploys PostgreSQL on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```console +$ helm delete my-release +``` + +The command removes all the Kubernetes components but PVC's associated with the chart and deletes the release. + +To delete the PVC's associated with `my-release`: + +```console +$ kubectl delete pvc -l release=my-release +``` + +> **Note**: Deleting the PVC's will delete postgresql data as well. Please be cautious before doing it. + +## Parameters + +The following tables lists the configurable parameters of the PostgreSQL chart and their default values. + +| Parameter | Description | Default | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| +| `global.imageRegistry` | Global Docker Image registry | `nil` | +| `global.postgresql.postgresqlDatabase` | PostgreSQL database (overrides `postgresqlDatabase`) | `nil` | +| `global.postgresql.postgresqlUsername` | PostgreSQL username (overrides `postgresqlUsername`) | `nil` | +| `global.postgresql.existingSecret` | Name of existing secret to use for PostgreSQL passwords (overrides `existingSecret`) | `nil` | +| `global.postgresql.postgresqlPassword` | PostgreSQL admin password (overrides `postgresqlPassword`) | `nil` | +| `global.postgresql.servicePort` | PostgreSQL port (overrides `service.port`) | `nil` | +| `global.postgresql.replicationPassword` | Replication user password (overrides `replication.password`) | `nil` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| `image.registry` | PostgreSQL Image registry | `docker.io` | +| `image.repository` | PostgreSQL Image name | `bitnami/postgresql` | +| `image.tag` | PostgreSQL Image tag | `{TAG_NAME}` | +| `image.pullPolicy` | PostgreSQL Image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify Image pull secrets | `nil` (does not add image pull secrets to deployed pods) | +| `image.debug` | Specify if debug values should be set | `false` | +| `nameOverride` | String to partially override common.names.fullname template with a string (will prepend the release name) | `nil` | +| `fullnameOverride` | String to fully override common.names.fullname template with a string | `nil` | +| `volumePermissions.enabled` | Enable init container that changes volume permissions in the data directory (for cases where the default k8s `runAsUser` and `fsUser` values do not work) | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `"10"` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | +| `volumePermissions.securityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `volumePermissions.securityContext.runAsUser` | User ID for the init container (when facing issues in OpenShift or uid unknown, try value "auto") | `0` | +| `usePasswordFile` | Have the secrets mounted as a file instead of env vars | `false` | +| `ldap.enabled` | Enable LDAP support | `false` | +| `ldap.existingSecret` | Name of existing secret to use for LDAP passwords | `nil` | +| `ldap.url` | LDAP URL beginning in the form `ldap[s]://host[:port]/basedn[?[attribute][?[scope][?[filter]]]]` | `nil` | +| `ldap.server` | IP address or name of the LDAP server. | `nil` | +| `ldap.port` | Port number on the LDAP server to connect to | `nil` | +| `ldap.scheme` | Set to `ldaps` to use LDAPS. | `nil` | +| `ldap.tls` | Set to `1` to use TLS encryption | `nil` | +| `ldap.prefix` | String to prepend to the user name when forming the DN to bind | `nil` | +| `ldap.suffix` | String to append to the user name when forming the DN to bind | `nil` | +| `ldap.search_attr` | Attribute to match against the user name in the search | `nil` | +| `ldap.search_filter` | The search filter to use when doing search+bind authentication | `nil` | +| `ldap.baseDN` | Root DN to begin the search for the user in | `nil` | +| `ldap.bindDN` | DN of user to bind to LDAP | `nil` | +| `ldap.bind_password` | Password for the user to bind to LDAP | `nil` | +| `replication.enabled` | Enable replication | `false` | +| `replication.user` | Replication user | `repl_user` | +| `replication.password` | Replication user password | `repl_password` | +| `replication.readReplicas` | Number of read replicas replicas | `1` | +| `replication.synchronousCommit` | Set synchronous commit mode. Allowed values: `on`, `remote_apply`, `remote_write`, `local` and `off` | `off` | +| `replication.numSynchronousReplicas` | Number of replicas that will have synchronous replication. Note: Cannot be greater than `replication.readReplicas`. | `0` | +| `replication.applicationName` | Cluster application name. Useful for advanced replication settings | `my_application` | +| `existingSecret` | Name of existing secret to use for PostgreSQL passwords. The secret has to contain the keys `postgresql-password` which is the password for `postgresqlUsername` when it is different of `postgres`, `postgresql-postgres-password` which will override `postgresqlPassword`, `postgresql-replication-password` which will override `replication.password` and `postgresql-ldap-password` which will be used to authenticate on LDAP. The value is evaluated as a template. | `nil` | +| `postgresqlPostgresPassword` | PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`, in which case`postgres` is the admin username). | _random 10 character alphanumeric string_ | +| `postgresqlUsername` | PostgreSQL user (creates a non-admin user when `postgresqlUsername` is not `postgres`) | `postgres` | +| `postgresqlPassword` | PostgreSQL user password | _random 10 character alphanumeric string_ | +| `postgresqlDatabase` | PostgreSQL database | `nil` | +| `postgresqlDataDir` | PostgreSQL data dir folder | `/bitnami/postgresql` (same value as persistence.mountPath) | +| `extraEnv` | Any extra environment variables you would like to pass on to the pod. The value is evaluated as a template. | `[]` | +| `extraEnvVarsCM` | Name of a Config Map containing extra environment variables you would like to pass on to the pod. The value is evaluated as a template. | `nil` | +| `postgresqlInitdbArgs` | PostgreSQL initdb extra arguments | `nil` | +| `postgresqlInitdbWalDir` | PostgreSQL location for transaction log | `nil` | +| `postgresqlConfiguration` | Runtime Config Parameters | `nil` | +| `postgresqlExtendedConf` | Extended Runtime Config Parameters (appended to main or default configuration) | `nil` | +| `pgHbaConfiguration` | Content of pg_hba.conf | `nil (do not create pg_hba.conf)` | +| `postgresqlSharedPreloadLibraries` | Shared preload libraries (comma-separated list) | `pgaudit` | +| `postgresqlMaxConnections` | Maximum total connections | `nil` | +| `postgresqlPostgresConnectionLimit` | Maximum total connections for the postgres user | `nil` | +| `postgresqlDbUserConnectionLimit` | Maximum total connections for the non-admin user | `nil` | +| `postgresqlTcpKeepalivesInterval` | TCP keepalives interval | `nil` | +| `postgresqlTcpKeepalivesIdle` | TCP keepalives idle | `nil` | +| `postgresqlTcpKeepalivesCount` | TCP keepalives count | `nil` | +| `postgresqlStatementTimeout` | Statement timeout | `nil` | +| `postgresqlPghbaRemoveFilters` | Comma-separated list of patterns to remove from the pg_hba.conf file | `nil` | +| `customStartupProbe` | Override default startup probe | `nil` | +| `customLivenessProbe` | Override default liveness probe | `nil` | +| `customReadinessProbe` | Override default readiness probe | `nil` | +| `audit.logHostname` | Add client hostnames to the log file | `false` | +| `audit.logConnections` | Add client log-in operations to the log file | `false` | +| `audit.logDisconnections` | Add client log-outs operations to the log file | `false` | +| `audit.pgAuditLog` | Add operations to log using the pgAudit extension | `nil` | +| `audit.clientMinMessages` | Message log level to share with the user | `nil` | +| `audit.logLinePrefix` | Template string for the log line prefix | `nil` | +| `audit.logTimezone` | Timezone for the log timestamps | `nil` | +| `configurationConfigMap` | ConfigMap with the PostgreSQL configuration files (Note: Overrides `postgresqlConfiguration` and `pgHbaConfiguration`). The value is evaluated as a template. | `nil` | +| `extendedConfConfigMap` | ConfigMap with the extended PostgreSQL configuration files. The value is evaluated as a template. | `nil` | +| `initdbScripts` | Dictionary of initdb scripts | `nil` | +| `initdbUser` | PostgreSQL user to execute the .sql and sql.gz scripts | `nil` | +| `initdbPassword` | Password for the user specified in `initdbUser` | `nil` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`). The value is evaluated as a template. | `nil` | +| `initdbScriptsSecret` | Secret with initdb scripts that contain sensitive information (Note: can be used with `initdbScriptsConfigMap` or `initdbScripts`). The value is evaluated as a template. | `nil` | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | PostgreSQL port | `5432` | +| `service.nodePort` | Kubernetes Service nodePort | `nil` | +| `service.annotations` | Annotations for PostgreSQL service | `{}` (evaluated as a template) | +| `service.loadBalancerIP` | loadBalancerIP if service type is `LoadBalancer` | `nil` | +| `service.loadBalancerSourceRanges` | Address that are allowed when svc is LoadBalancer | `[]` (evaluated as a template) | +| `schedulerName` | Name of the k8s scheduler (other than default) | `nil` | +| `shmVolume.enabled` | Enable emptyDir volume for /dev/shm for primary and read replica(s) Pod(s) | `true` | +| `shmVolume.chmod.enabled` | Run at init chmod 777 of the /dev/shm (ignored if `volumePermissions.enabled` is `false`) | `true` | +| `persistence.enabled` | Enable persistence using PVC | `true` | +| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim`, the value is evaluated as a template. | `nil` | +| `persistence.mountPath` | Path to mount the volume at | `/bitnami/postgresql` | +| `persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `persistence.storageClass` | PVC Storage Class for PostgreSQL volume | `nil` | +| `persistence.accessModes` | PVC Access Mode for PostgreSQL volume | `[ReadWriteOnce]` | +| `persistence.size` | PVC Storage Request for PostgreSQL volume | `8Gi` | +| `persistence.annotations` | Annotations for the PVC | `{}` | +| `persistence.selector` | Selector to match an existing Persistent Volume (this value is evaluated as a template) | `{}` | +| `commonAnnotations` | Annotations to be added to all deployed resources (rendered as a template) | `{}` | +| `primary.podAffinityPreset` | PostgreSQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.podAntiAffinityPreset` | PostgreSQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `primary.nodeAffinityPreset.type` | PostgreSQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.nodeAffinityPreset.key` | PostgreSQL primary node label key to match Ignored if `primary.affinity` is set. | `""` | +| `primary.nodeAffinityPreset.values` | PostgreSQL primary node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `primary.affinity` | Affinity for PostgreSQL primary pods assignment | `{}` (evaluated as a template) | +| `primary.nodeSelector` | Node labels for PostgreSQL primary pods assignment | `{}` (evaluated as a template) | +| `primary.tolerations` | Tolerations for PostgreSQL primary pods assignment | `[]` (evaluated as a template) | +| `primary.anotations` | Map of annotations to add to the statefulset (postgresql primary) | `{}` | +| `primary.labels` | Map of labels to add to the statefulset (postgresql primary) | `{}` | +| `primary.podAnnotations` | Map of annotations to add to the pods (postgresql primary) | `{}` | +| `primary.podLabels` | Map of labels to add to the pods (postgresql primary) | `{}` | +| `primary.priorityClassName` | Priority Class to use for each pod (postgresql primary) | `nil` | +| `primary.extraInitContainers` | Additional init containers to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumes` | Additional volumes to add to the pods (postgresql primary) | `[]` | +| `primary.sidecars` | Add additional containers to the pod | `[]` | +| `primary.service.type` | Allows using a different service type for primary | `nil` | +| `primary.service.nodePort` | Allows using a different nodePort for primary | `nil` | +| `primary.service.clusterIP` | Allows using a different clusterIP for primary | `nil` | +| `primaryAsStandBy.enabled` | Whether to enable current cluster's primary as standby server of another cluster or not. | `false` | +| `primaryAsStandBy.primaryHost` | The Host of replication primary in the other cluster. | `nil` | +| `primaryAsStandBy.primaryPort ` | The Port of replication primary in the other cluster. | `nil` | +| `readReplicas.podAffinityPreset` | PostgreSQL read only pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.podAntiAffinityPreset` | PostgreSQL read only pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `readReplicas.nodeAffinityPreset.type` | PostgreSQL read only node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.nodeAffinityPreset.key` | PostgreSQL read only node label key to match Ignored if `primary.affinity` is set. | `""` | +| `readReplicas.nodeAffinityPreset.values` | PostgreSQL read only node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `readReplicas.affinity` | Affinity for PostgreSQL read only pods assignment | `{}` (evaluated as a template) | +| `readReplicas.nodeSelector` | Node labels for PostgreSQL read only pods assignment | `{}` (evaluated as a template) | +| `readReplicas.anotations` | Map of annotations to add to the statefulsets (postgresql readReplicas) | `{}` | +| `readReplicas.resources` | CPU/Memory resource requests/limits override for readReplicass. Will fallback to `values.resources` if not defined. | `{}` | +| `readReplicas.labels` | Map of labels to add to the statefulsets (postgresql readReplicas) | `{}` | +| `readReplicas.podAnnotations` | Map of annotations to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.podLabels` | Map of labels to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.priorityClassName` | Priority Class to use for each pod (postgresql readReplicas) | `nil` | +| `readReplicas.extraInitContainers` | Additional init containers to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumeMounts` | Additional volume mounts to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumes` | Additional volumes to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.sidecars` | Add additional containers to the pod | `[]` | +| `readReplicas.service.type` | Allows using a different service type for readReplicas | `nil` | +| `readReplicas.service.nodePort` | Allows using a different nodePort for readReplicas | `nil` | +| `readReplicas.service.clusterIP` | Allows using a different clusterIP for readReplicas | `nil` | +| `readReplicas.persistence.enabled` | Whether to enable readReplicas replicas persistence | `true` | +| `terminationGracePeriodSeconds` | Seconds the pod needs to terminate gracefully | `nil` | +| `resources` | CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `250m` | +| `securityContext.*` | Other pod security context to be included as-is in the pod spec | `{}` | +| `securityContext.enabled` | Enable security context | `true` | +| `securityContext.fsGroup` | Group ID for the pod | `1001` | +| `containerSecurityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `containerSecurityContext.enabled` | Enable container security context | `true` | +| `containerSecurityContext.runAsUser` | User ID for the container | `1001` | +| `serviceAccount.enabled` | Enable service account (Note: Service Account will only be automatically created if `serviceAccount.name` is not set) | `false` | +| `serviceAccount.name` | Name of existing service account | `nil` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.explicitNamespacesSelector` | A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed | `{}` | +| `startupProbe.enabled` | Enable startupProbe | `false` | +| `startupProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `startupProbe.periodSeconds` | How often to perform the probe | 15 | +| `startupProbe.timeoutSeconds` | When the probe times | 5 | +| `startupProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 10 | +| `startupProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | 1 | +| `livenessProbe.enabled` | Enable livenessProbe | `true` | +| `livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `livenessProbe.periodSeconds` | How often to perform the probe | 10 | +| `livenessProbe.timeoutSeconds` | When the probe times out | 5 | +| `livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `readinessProbe.enabled` | Enable readinessProbe | `true` | +| `readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated | 5 | +| `readinessProbe.periodSeconds` | How often to perform the probe | 10 | +| `readinessProbe.timeoutSeconds` | When the probe times out | 5 | +| `readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `tls.enabled` | Enable TLS traffic support | `false` | +| `tls.preferServerCiphers` | Whether to use the server's TLS cipher preferences rather than the client's | `true` | +| `tls.certificatesSecret` | Name of an existing secret that contains the certificates | `nil` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename. If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate. | `nil` | +| `tls.crlFilename` | File containing a Certificate Revocation List | `nil` | +| `metrics.enabled` | Start a prometheus exporter | `false` | +| `metrics.service.type` | Kubernetes Service type | `ClusterIP` | +| `service.clusterIP` | Static clusterIP or None for headless services | `nil` | +| `metrics.service.annotations` | Additional annotations for metrics exporter pod | `{ prometheus.io/scrape: "true", prometheus.io/port: "9187"}` | +| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` | +| `metrics.serviceMonitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` | +| `metrics.serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.namespace` | Optional namespace in which to create ServiceMonitor | `nil` | +| `metrics.serviceMonitor.interval` | Scrape interval. If not set, the Prometheus default scrape interval is used | `nil` | +| `metrics.serviceMonitor.scrapeTimeout` | Scrape timeout. If not set, the Prometheus default scrape timeout is used | `nil` | +| `metrics.prometheusRule.enabled` | Set this to true to create prometheusRules for Prometheus operator | `false` | +| `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so prometheusRules will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.namespace` | namespace where prometheusRules resource should be created | the same namespace as postgresql | +| `metrics.prometheusRule.rules` | [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) to be created, check values for an example. | `[]` | +| `metrics.image.registry` | PostgreSQL Exporter Image registry | `docker.io` | +| `metrics.image.repository` | PostgreSQL Exporter Image name | `bitnami/postgres-exporter` | +| `metrics.image.tag` | PostgreSQL Exporter Image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | PostgreSQL Exporter Image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify Image pull secrets | `nil` (does not add image pull secrets to deployed pods) | +| `metrics.customMetrics` | Additional custom metrics | `nil` | +| `metrics.extraEnvVars` | Extra environment variables to add to exporter | `{}` (evaluated as a template) | +| `metrics.securityContext.*` | Other container security context to be included as-is in the container spec | `{}` | +| `metrics.securityContext.enabled` | Enable security context for metrics | `false` | +| `metrics.securityContext.runAsUser` | User ID for the container for metrics | `1001` | +| `metrics.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 30 | +| `metrics.livenessProbe.periodSeconds` | How often to perform the probe | 10 | +| `metrics.livenessProbe.timeoutSeconds` | When the probe times out | 5 | +| `metrics.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `metrics.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `metrics.readinessProbe.enabled` | would you like a readinessProbe to be enabled | `true` | +| `metrics.readinessProbe.initialDelaySeconds` | Delay before liveness probe is initiated | 5 | +| `metrics.readinessProbe.periodSeconds` | How often to perform the probe | 10 | +| `metrics.readinessProbe.timeoutSeconds` | When the probe times out | 5 | +| `metrics.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | 6 | +| `metrics.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed | 1 | +| `updateStrategy` | Update strategy policy | `{type: "RollingUpdate"}` | +| `psp.create` | Create Pod Security Policy | `false` | +| `rbac.create` | Create Role and RoleBinding (required for PSP to work) | `false` | +| `extraDeploy` | Array of extra objects to deploy with the release (evaluated as a template). | `nil` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +$ helm install my-release \ + --set postgresqlPassword=secretpassword,postgresqlDatabase=my-database \ + bitnami/postgresql +``` + +The above command sets the PostgreSQL `postgres` account password to `secretpassword`. Additionally it creates a database named `my-database`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +$ helm install my-release -f values.yaml bitnami/postgresql +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Configuration and installation details + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Customizing primary and read replica services in a replicated configuration + +At the top level, there is a service object which defines the services for both primary and readReplicas. For deeper customization, there are service objects for both the primary and read types individually. This allows you to override the values in the top level service object so that the primary and read can be of different service types and with different clusterIPs / nodePorts. Also in the case you want the primary and read to be of type nodePort, you will need to set the nodePorts to different values to prevent a collision. The values that are deeper in the primary.service or readReplicas.service objects will take precedence over the top level service object. + +### Change PostgreSQL version + +To modify the PostgreSQL version used in this chart you can specify a [valid image tag](https://hub.docker.com/r/bitnami/postgresql/tags/) using the `image.tag` parameter. For example, `image.tag=X.Y.Z`. This approach is also applicable to other images like exporters. + +### postgresql.conf / pg_hba.conf files as configMap + +This helm chart also supports to customize the whole configuration file. + +Add your custom file to "files/postgresql.conf" in your working directory. This file will be mounted as configMap to the containers and it will be used for configuring the PostgreSQL server. + +Alternatively, you can add additional PostgreSQL configuration parameters using the `postgresqlExtendedConf` parameter as a dict, using camelCase, e.g. {"sharedBuffers": "500MB"}. Alternatively, to replace the entire default configuration use `postgresqlConfiguration`. + +In addition to these options, you can also set an external ConfigMap with all the configuration files. This is done by setting the `configurationConfigMap` parameter. Note that this will override the two previous options. + +### Allow settings to be loaded from files other than the default `postgresql.conf` + +If you don't want to provide the whole PostgreSQL configuration file and only specify certain parameters, you can add your extended `.conf` files to "files/conf.d/" in your working directory. +Those files will be mounted as configMap to the containers adding/overwriting the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +Alternatively, you can also set an external ConfigMap with all the extra configuration files. This is done by setting the `extendedConfConfigMap` parameter. Note that this will override the previous option. + +### Initialize a fresh instance + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, they must be located inside the chart folder `files/docker-entrypoint-initdb.d` so they can be consumed as a ConfigMap. + +Alternatively, you can specify custom scripts using the `initdbScripts` parameter as dict. + +In addition to these options, you can also set an external ConfigMap with all the initialization scripts. This is done by setting the `initdbScriptsConfigMap` parameter. Note that this will override the two previous options. If your initialization scripts contain sensitive information such as credentials or passwords, you can use the `initdbScriptsSecret` parameter. + +The allowed extensions are `.sh`, `.sql` and `.sql.gz`. + +### Securing traffic using TLS + +TLS support can be enabled in the chart by specifying the `tls.` parameters while creating a release. The following parameters should be configured to properly enable the TLS support in the chart: + +- `tls.enabled`: Enable TLS support. Defaults to `false` +- `tls.certificatesSecret`: Name of an existing secret that contains the certificates. No defaults. +- `tls.certFilename`: Certificate filename. No defaults. +- `tls.certKeyFilename`: Certificate key filename. No defaults. + +For example: + +* First, create the secret with the cetificates files: + + ```console + kubectl create secret generic certificates-tls-secret --from-file=./cert.crt --from-file=./cert.key --from-file=./ca.crt + ``` + +* Then, use the following parameters: + + ```console + volumePermissions.enabled=true + tls.enabled=true + tls.certificatesSecret="certificates-tls-secret" + tls.certFilename="cert.crt" + tls.certKeyFilename="cert.key" + ``` + + > Note TLS and VolumePermissions: PostgreSQL requires certain permissions on sensitive files (such as certificate keys) to start up. Due to an on-going [issue](https://github.com/kubernetes/kubernetes/issues/57923) regarding kubernetes permissions and the use of `containerSecurityContext.runAsUser`, you must enable `volumePermissions` to ensure everything works as expected. + +### Sidecars + +If you need additional containers to run within the same pod as PostgreSQL (e.g. an additional metrics or logging exporter), you can do so via the `sidecars` config parameter. Simply define your container according to the Kubernetes container spec. + +```yaml +# For the PostgreSQL primary +primary: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +# For the PostgreSQL replicas +readReplicas: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +### Metrics + +The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9187) is not exposed and it is expected that the metrics are collected from inside the k8s cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). + +The exporter allows to create custom metrics from additional SQL queries. See the Chart's `values.yaml` for an example and consult the [exporters documentation](https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file) for more details. + +### Use of global variables + +In more complex scenarios, we may have the following tree of dependencies + +``` + +--------------+ + | | + +------------+ Chart 1 +-----------+ + | | | | + | --------+------+ | + | | | + | | | + | | | + | | | + v v v ++-------+------+ +--------+------+ +--------+------+ +| | | | | | +| PostgreSQL | | Sub-chart 1 | | Sub-chart 2 | +| | | | | | ++--------------+ +---------------+ +---------------+ +``` + +The three charts below depend on the parent chart Chart 1. However, subcharts 1 and 2 may need to connect to PostgreSQL as well. In order to do so, subcharts 1 and 2 need to know the PostgreSQL credentials, so one option for deploying could be deploy Chart 1 with the following parameters: + +``` +postgresql.postgresqlPassword=testtest +subchart1.postgresql.postgresqlPassword=testtest +subchart2.postgresql.postgresqlPassword=testtest +postgresql.postgresqlDatabase=db1 +subchart1.postgresql.postgresqlDatabase=db1 +subchart2.postgresql.postgresqlDatabase=db1 +``` + +If the number of dependent sub-charts increases, installing the chart with parameters can become increasingly difficult. An alternative would be to set the credentials using global variables as follows: + +``` +global.postgresql.postgresqlPassword=testtest +global.postgresql.postgresqlDatabase=db1 +``` + +This way, the credentials will be available in all of the subcharts. + +## Persistence + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image stores the PostgreSQL data and configurations at the `/bitnami/postgresql` path of the container. + +Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. +See the [Parameters](#parameters) section to configure the PVC or to disable persistence. + +If you already have data in it, you will fail to sync to standby nodes for all commits, details can refer to [code](https://github.com/bitnami/bitnami-docker-postgresql/blob/8725fe1d7d30ebe8d9a16e9175d05f7ad9260c93/9.6/debian-9/rootfs/libpostgresql.sh#L518-L556). If you need to use those data, please covert them to sql and import after `helm install` finished. + +## NetworkPolicy + +To enable network policy for PostgreSQL, install [a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), and set `networkPolicy.enabled` to `true`. + +For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: + +```console +$ kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" +``` + +With NetworkPolicy enabled, traffic will be limited to just port 5432. + +For more precise policy, set `networkPolicy.allowExternal=false`. This will only allow pods with the generated client label to connect to PostgreSQL. +This label will be displayed in the output of a successful install. + +## Differences between Bitnami PostgreSQL image and [Docker Official](https://hub.docker.com/_/postgres) image + +- The Docker Official PostgreSQL image does not support replication. If you pass any replication environment variable, this would be ignored. The only environment variables supported by the Docker Official image are POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD, POSTGRES_INITDB_ARGS, POSTGRES_INITDB_WALDIR and PGDATA. All the remaining environment variables are specific to the Bitnami PostgreSQL image. +- The Bitnami PostgreSQL image is non-root by default. This requires that you run the pod with `securityContext` and updates the permissions of the volume with an `initContainer`. A key benefit of this configuration is that the pod follows security best practices and is prepared to run on Kubernetes distributions with hard security constraints like OpenShift. +- For OpenShift, one may either define the runAsUser and fsGroup accordingly, or try this more dynamic option: volumePermissions.securityContext.runAsUser="auto",securityContext.enabled=false,containerSecurityContext.enabled=false,shmVolume.chmod.enabled=false + +### Deploy chart using Docker Official PostgreSQL Image + +From chart version 4.0.0, it is possible to use this chart with the Docker Official PostgreSQL image. +Besides specifying the new Docker repository and tag, it is important to modify the PostgreSQL data directory and volume mount point. Basically, the PostgreSQL data dir cannot be the mount point directly, it has to be a subdirectory. + +``` +image.repository=postgres +image.tag=10.6 +postgresqlDataDir=/data/pgdata +persistence.mountPath=/data/ +``` + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` paremeter(s). Find more infomation about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/master/bitnami/common#affinities) chart. To do so, set the `XXX.podAffinityPreset`, `XXX.podAntiAffinityPreset`, or `XXX.nodeAffinityPreset` parameters. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami’s Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +It's necessary to specify the existing passwords while performing an upgrade to ensure the secrets are not updated with invalid randomly generated passwords. Remember to specify the existing values of the `postgresqlPassword` and `replication.password` parameters when upgrading the chart: + +```bash +$ helm upgrade my-release bitnami/postgresql \ + --set postgresqlPassword=[POSTGRESQL_PASSWORD] \ + --set replication.password=[REPLICATION_PASSWORD] +``` + +> Note: you need to substitute the placeholders _[POSTGRESQL_PASSWORD]_, and _[REPLICATION_PASSWORD]_ with the values obtained from instructions in the installation notes. + +### To 10.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Move dependency information from the *requirements.yaml* to the *Chart.yaml* +- After running `helm dependency update`, a *Chart.lock* file is generated containing the same structure used in the previous *requirements.lock* +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Chart. + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +#### Breaking changes + +- The term `master` has been replaced with `primary` and `slave` with `readReplicas` throughout the chart. Role names have changed from `master` and `slave` to `primary` and `read`. + +To upgrade to `10.0.0`, it should be done reusing the PVCs used to hold the PostgreSQL data on your previous release. To do so, follow the instructions below (the following example assumes that the release name is `postgresql`): + +> NOTE: Please, create a backup of your database before running any of those actions. + +Obtain the credentials and the names of the PVCs used to hold the PostgreSQL data on your current release: + +```console +$ export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +$ export POSTGRESQL_PVC=$(kubectl get pvc -l app.kubernetes.io/instance=postgresql,role=master -o jsonpath="{.items[0].metadata.name}") +``` + +Delete the PostgreSQL statefulset. Notice the option `--cascade=false`: + +```console +$ kubectl delete statefulsets.apps postgresql-postgresql --cascade=false +``` + +Now the upgrade works: + +```console +$ helm upgrade postgresql bitnami/postgresql --set postgresqlPassword=$POSTGRESQL_PASSWORD --set persistence.existingClaim=$POSTGRESQL_PVC +``` + +You will have to delete the existing PostgreSQL pod and the new statefulset is going to create a new one + +```console +$ kubectl delete pod postgresql-postgresql-0 +``` + +Finally, you should see the lines below in PostgreSQL container logs: + +```console +$ kubectl logs $(kubectl get pods -l app.kubernetes.io/instance=postgresql,app.kubernetes.io/name=postgresql,role=primary -o jsonpath="{.items[0].metadata.name}") +... +postgresql 08:05:12.59 INFO ==> Deploying PostgreSQL with persisted data... +... +``` + +### To 9.0.0 + +In this version the chart was adapted to follow the Helm label best practices, see [PR 3021](https://github.com/bitnami/charts/pull/3021). That means the backward compatibility is not guarantee when upgrading the chart to this major version. + +As a workaround, you can delete the existing statefulset (using the `--cascade=false` flag pods are not deleted) before upgrade the chart. For example, this can be a valid workflow: + +- Deploy an old version (8.X.X) + +```console +$ helm install postgresql bitnami/postgresql --version 8.10.14 +``` + +- Old version is up and running + +```console +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 1 2020-08-04 13:39:54.783480286 +0000 UTC deployed postgresql-8.10.14 11.8.0 + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 76s +``` + +- The upgrade to the latest one (9.X.X) is going to fail + +```console +$ helm upgrade postgresql bitnami/postgresql +Error: UPGRADE FAILED: cannot patch "postgresql-postgresql" with kind StatefulSet: StatefulSet.apps "postgresql-postgresql" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden +``` + +- Delete the statefulset + +```console +$ kubectl delete statefulsets.apps --cascade=false postgresql-postgresql +statefulset.apps "postgresql-postgresql" deleted +``` + +- Now the upgrade works + +```console +$ helm upgrade postgresql bitnami/postgresql +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 3 2020-08-04 13:42:08.020385884 +0000 UTC deployed postgresql-9.1.2 11.8.0 +``` + +- We can kill the existing pod and the new statefulset is going to create a new one: + +```console +$ kubectl delete pod postgresql-postgresql-0 +pod "postgresql-postgresql-0" deleted + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 19s +``` + +Please, note that without the `--cascade=false` both objects (statefulset and pod) are going to be removed and both objects will be deployed again with the `helm upgrade` command + +### To 8.0.0 + +Prefixes the port names with their protocols to comply with Istio conventions. + +If you depend on the port names in your setup, make sure to update them to reflect this change. + +### To 7.1.0 + +Adds support for LDAP configuration. + +### To 7.0.0 + +Helm performs a lookup for the object based on its group (apps), version (v1), and kind (Deployment). Also known as its GroupVersionKind, or GVK. Changing the GVK is considered a compatibility breaker from Kubernetes' point of view, so you cannot "upgrade" those objects to the new GVK in-place. Earlier versions of Helm 3 did not perform the lookup correctly which has since been fixed to match the spec. + +In https://github.com/helm/charts/pull/17281 the `apiVersion` of the statefulset resources was updated to `apps/v1` in tune with the api's deprecated, resulting in compatibility breakage. + +This major version bump signifies this change. + +### To 6.5.7 + +In this version, the chart will use PostgreSQL with the Postgis extension included. The version used with Postgresql version 10, 11 and 12 is Postgis 2.5. It has been compiled with the following dependencies: + +- protobuf +- protobuf-c +- json-c +- geos +- proj + +### To 5.0.0 + +In this version, the **chart is using PostgreSQL 11 instead of PostgreSQL 10**. You can find the main difference and notable changes in the following links: [https://www.postgresql.org/about/news/1894/](https://www.postgresql.org/about/news/1894/) and [https://www.postgresql.org/about/featurematrix/](https://www.postgresql.org/about/featurematrix/). + +For major releases of PostgreSQL, the internal data storage format is subject to change, thus complicating upgrades, you can see some errors like the following one in the logs: + +```console +Welcome to the Bitnami postgresql container +Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-postgresql +Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-postgresql/issues +Send us your feedback at containers@bitnami.com + +INFO ==> ** Starting PostgreSQL setup ** +NFO ==> Validating settings in POSTGRESQL_* env vars.. +INFO ==> Initializing PostgreSQL database... +INFO ==> postgresql.conf file not detected. Generating it... +INFO ==> pg_hba.conf file not detected. Generating it... +INFO ==> Deploying PostgreSQL with persisted data... +INFO ==> Configuring replication parameters +INFO ==> Loading custom scripts... +INFO ==> Enabling remote connections +INFO ==> Stopping PostgreSQL... +INFO ==> ** PostgreSQL setup finished! ** + +INFO ==> ** Starting PostgreSQL ** + [1] FATAL: database files are incompatible with server + [1] DETAIL: The data directory was initialized by PostgreSQL version 10, which is not compatible with this version 11.3. +``` + +In this case, you should migrate the data from the old chart to the new one following an approach similar to that described in [this section](https://www.postgresql.org/docs/current/upgrading.html#UPGRADING-VIA-PGDUMPALL) from the official documentation. Basically, create a database dump in the old chart, move and restore it in the new one. + +### To 4.0.0 + +This chart will use by default the Bitnami PostgreSQL container starting from version `10.7.0-r68`. This version moves the initialization logic from node.js to bash. This new version of the chart requires setting the `POSTGRES_PASSWORD` in the slaves as well, in order to properly configure the `pg_hba.conf` file. Users from previous versions of the chart are advised to upgrade immediately. + +IMPORTANT: If you do not want to upgrade the chart version then make sure you use the `10.7.0-r68` version of the container. Otherwise, you will get this error + +``` +The POSTGRESQL_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development +``` + +### To 3.0.0 + +This releases make it possible to specify different nodeSelector, affinity and tolerations for master and slave pods. +It also fixes an issue with `postgresql.master.fullname` helper template not obeying fullnameOverride. + +#### Breaking changes + +- `affinty` has been renamed to `master.affinity` and `slave.affinity`. +- `tolerations` has been renamed to `master.tolerations` and `slave.tolerations`. +- `nodeSelector` has been renamed to `master.nodeSelector` and `slave.nodeSelector`. + +### To 2.0.0 + +In order to upgrade from the `0.X.X` branch to `1.X.X`, you should follow the below steps: + +- Obtain the service name (`SERVICE_NAME`) and password (`OLD_PASSWORD`) of the existing postgresql chart. You can find the instructions to obtain the password in the NOTES.txt, the service name can be obtained by running + +```console +$ kubectl get svc +``` + +- Install (not upgrade) the new version + +```console +$ helm repo update +$ helm install my-release bitnami/postgresql +``` + +- Connect to the new pod (you can obtain the name by running `kubectl get pods`): + +```console +$ kubectl exec -it NAME bash +``` + +- Once logged in, create a dump file from the previous database using `pg_dump`, for that we should connect to the previous postgresql chart: + +```console +$ pg_dump -h SERVICE_NAME -U postgres DATABASE_NAME > /tmp/backup.sql +``` + +After run above command you should be prompted for a password, this password is the previous chart password (`OLD_PASSWORD`). +This operation could take some time depending on the database size. + +- Once you have the backup file, you can restore it with a command like the one below: + +```console +$ psql -U postgres DATABASE_NAME < /tmp/backup.sql +``` + +In this case, you are accessing to the local postgresql, so the password should be the new one (you can find it in NOTES.txt). + +If you want to restore the database and the database schema does not exist, it is necessary to first follow the steps described below. + +```console +$ psql -U postgres +postgres=# drop database DATABASE_NAME; +postgres=# create database DATABASE_NAME; +postgres=# create user USER_NAME; +postgres=# alter role USER_NAME with password 'BITNAMI_USER_PASSWORD'; +postgres=# grant all privileges on database DATABASE_NAME to USER_NAME; +postgres=# alter database DATABASE_NAME owner to USER_NAME; +``` diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/.helmignore b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/.helmignore new file mode 100644 index 0000000000..50af031725 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/Chart.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/Chart.yaml new file mode 100644 index 0000000000..bcc3808d08 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.4.2 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/master/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: common +sources: +- https://github.com/bitnami/charts +- http://www.bitnami.com/ +type: library +version: 1.4.2 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/README.md new file mode 100644 index 0000000000..7287cbb5fc --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/README.md @@ -0,0 +1,322 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.1.0 + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|-------------------------------|------------------------------------------------------|------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.node.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pod.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pod.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|----------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|--------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Return the proper Docker Image Registry Secret Names | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Inpput | +|-------------------------|------------------------------------------------------------|-------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|---------------------------|--------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | +| `common.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|---------------------------|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "subchart" "subchart" "context" $` secret, field and subchart are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for RedisTM are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB® are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|------------------------------|----------------------------------|------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets. + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 --decode) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 --decode) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl new file mode 100644 index 0000000000..493a6dc7e4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl @@ -0,0 +1,94 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100644 index 0000000000..4dde56a38d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl @@ -0,0 +1,95 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100644 index 0000000000..a79cc2e322 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: You must provide your current passwords when upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl new file mode 100644 index 0000000000..60f04fd6e2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl @@ -0,0 +1,47 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl new file mode 100644 index 0000000000..622ef50e3c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl @@ -0,0 +1,42 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if typeIs "int" .servicePort }} + number: {{ .servicePort }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100644 index 0000000000..252066c7e2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl new file mode 100644 index 0000000000..adf2a74f48 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | 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 "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100644 index 0000000000..60b84a7019 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl @@ -0,0 +1,129 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- if index $secret.data .key }} + {{- $password = index $secret.data .key }} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100644 index 0000000000..60e2a844f6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000000..2db166851b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100644 index 0000000000..ea083a249f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ .context.Release.Namespace | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 --decode) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000000..ae10fa41ee --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000000..8679ddffb1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000000..bb5ed7253d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000000..7d5ecbccb4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB(R) required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB(R) values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB(R) is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (not $existingSecret) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB(R) is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000000..992bcd3908 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,131 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000000..3e2a47c039 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,72 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis(TM) required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $existingSecret := include "common.redis.values.existingSecret" . -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $valueKeyRedisPassword := printf "%s%s" $valueKeyPrefix "password" -}} + {{- $valueKeyRedisUsePassword := printf "%s%s" $valueKeyPrefix "usePassword" -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $usePassword := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUsePassword "context" .context) -}} + {{- if eq $usePassword "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Redis Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.redis.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Redis(TM) is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.redis.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000000..9a814cf40d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "subchart" "subchart" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s%s=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/values.yaml new file mode 100644 index 0000000000..9ecdc93f58 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/charts/common/values.yaml @@ -0,0 +1,3 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +exampleValue: common-chart diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml new file mode 100644 index 0000000000..97e18a4cc0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml @@ -0,0 +1,3 @@ +commonAnnotations: + helm.sh/hook: "\"pre-install, pre-upgrade\"" + helm.sh/hook-weight: "-1" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/default-values.yaml new file mode 100644 index 0000000000..fc2ba605ad --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/default-values.yaml @@ -0,0 +1 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml new file mode 100644 index 0000000000..347d3b40a8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml @@ -0,0 +1,2 @@ +shmVolume: + enabled: false diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/README.md new file mode 100644 index 0000000000..1813a2feaa --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/README.md @@ -0,0 +1 @@ +Copy here your postgresql.conf and/or pg_hba.conf files to use it as a config map. diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/conf.d/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/conf.d/README.md new file mode 100644 index 0000000000..184c1875d5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/conf.d/README.md @@ -0,0 +1,4 @@ +If you don't want to provide the whole configuration file and only specify certain parameters, you can copy here your extended `.conf` files. +These files will be injected as a config maps and add/overwrite the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +More info in the [bitnami-docker-postgresql README](https://github.com/bitnami/bitnami-docker-postgresql#configuration-file). diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md new file mode 100644 index 0000000000..cba38091e0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md @@ -0,0 +1,3 @@ +You can copy here your custom `.sh`, `.sql` or `.sql.gz` file so they are executed during the first boot of the image. + +More info in the [bitnami-docker-postgresql](https://github.com/bitnami/bitnami-docker-postgresql#initializing-a-new-instance) repository. \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/NOTES.txt new file mode 100644 index 0000000000..4e98958c13 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/NOTES.txt @@ -0,0 +1,59 @@ +** Please be patient while the chart is being deployed ** + +PostgreSQL can be accessed via port {{ template "postgresql.port" . }} on the following DNS name from within your cluster: + + {{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local - Read/Write connection +{{- if .Values.replication.enabled }} + {{ template "common.names.fullname" . }}-read.{{ .Release.Namespace }}.svc.cluster.local - Read only connection +{{- end }} + +{{- if not (eq (include "postgresql.username" .) "postgres") }} + +To get the password for "postgres" run: + + export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-postgres-password}" | base64 --decode) +{{- end }} + +To get the password for "{{ template "postgresql.username" . }}" run: + + export POSTGRES_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-password}" | base64 --decode) + +To connect to your database run the following command: + + kubectl run {{ template "common.names.fullname" . }}-client --rm --tty -i --restart='Never' --namespace {{ .Release.Namespace }} --image {{ template "postgresql.image" . }} --env="PGPASSWORD=$POSTGRES_PASSWORD" {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} + --labels="{{ template "common.names.fullname" . }}-client=true" {{- end }} --command -- psql --host {{ template "common.names.fullname" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.port" . }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label {{ template "common.names.fullname" . }}-client=true" will be able to connect to this PostgreSQL cluster. +{{- end }} + +To connect to your database from outside the cluster execute the following commands: + +{{- if contains "NodePort" .Values.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "common.names.fullname" . }}) + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $NODE_IP --port $NODE_PORT -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} + +{{- else if contains "LoadBalancer" .Values.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $SERVICE_IP --port {{ template "postgresql.port" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} + +{{- else if contains "ClusterIP" .Values.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "common.names.fullname" . }} {{ template "postgresql.port" . }}:{{ template "postgresql.port" . }} & + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host 127.0.0.1 -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.port" . }} + +{{- end }} + +{{- include "postgresql.validateValues" . -}} + +{{- include "common.warnings.rollingTag" .Values.image -}} + +{{- $passwordValidationErrors := include "common.validations.values.postgresql.passwords" (dict "secret" (include "common.names.fullname" .) "context" $) -}} + +{{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $passwordValidationErrors) "context" $) -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/_helpers.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 0000000000..1f98efe789 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/_helpers.tpl @@ -0,0 +1,337 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "postgresql.name" -}} +{{- default .Chart.Name .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). +*/}} +{{- define "postgresql.primary.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- $fullname := default (printf "%s-%s" .Release.Name $name) .Values.fullnameOverride -}} +{{- if .Values.replication.enabled -}} +{{- printf "%s-%s" $fullname "primary" | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $fullname | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper PostgreSQL image name +*/}} +{{- define "postgresql.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper PostgreSQL metrics image name +*/}} +{{- define "postgresql.metrics.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "postgresql.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "postgresql.imagePullSecrets" -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image .Values.volumePermissions.image) "global" .Values.global) }} +{{- end -}} + +{{/* +Return PostgreSQL postgres user password +*/}} +{{- define "postgresql.postgres.password" -}} +{{- if .Values.global.postgresql.postgresqlPostgresPassword }} + {{- .Values.global.postgresql.postgresqlPostgresPassword -}} +{{- else if .Values.postgresqlPostgresPassword -}} + {{- .Values.postgresqlPostgresPassword -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL password +*/}} +{{- define "postgresql.password" -}} +{{- if .Values.global.postgresql.postgresqlPassword }} + {{- .Values.global.postgresql.postgresqlPassword -}} +{{- else if .Values.postgresqlPassword -}} + {{- .Values.postgresqlPassword -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication password +*/}} +{{- define "postgresql.replication.password" -}} +{{- if .Values.global.postgresql.replicationPassword }} + {{- .Values.global.postgresql.replicationPassword -}} +{{- else if .Values.replication.password -}} + {{- .Values.replication.password -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL username +*/}} +{{- define "postgresql.username" -}} +{{- if .Values.global.postgresql.postgresqlUsername }} + {{- .Values.global.postgresql.postgresqlUsername -}} +{{- else -}} + {{- .Values.postgresqlUsername -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication username +*/}} +{{- define "postgresql.replication.username" -}} +{{- if .Values.global.postgresql.replicationUser }} + {{- .Values.global.postgresql.replicationUser -}} +{{- else -}} + {{- .Values.replication.user -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL port +*/}} +{{- define "postgresql.port" -}} +{{- if .Values.global.postgresql.servicePort }} + {{- .Values.global.postgresql.servicePort -}} +{{- else -}} + {{- .Values.service.port -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL created database +*/}} +{{- define "postgresql.database" -}} +{{- if .Values.global.postgresql.postgresqlDatabase }} + {{- .Values.global.postgresql.postgresqlDatabase -}} +{{- else if .Values.postgresqlDatabase -}} + {{- .Values.postgresqlDatabase -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "postgresql.secretName" -}} +{{- if .Values.global.postgresql.existingSecret }} + {{- printf "%s" (tpl .Values.global.postgresql.existingSecret $) -}} +{{- else if .Values.existingSecret -}} + {{- printf "%s" (tpl .Values.existingSecret $) -}} +{{- else -}} + {{- printf "%s" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if we should use an existingSecret. +*/}} +{{- define "postgresql.useExistingSecret" -}} +{{- if or .Values.global.postgresql.existingSecret .Values.existingSecret -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created +*/}} +{{- define "postgresql.createSecret" -}} +{{- if not (include "postgresql.useExistingSecret" .) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the configuration ConfigMap name. +*/}} +{{- define "postgresql.configurationCM" -}} +{{- if .Values.configurationConfigMap -}} +{{- printf "%s" (tpl .Values.configurationConfigMap $) -}} +{{- else -}} +{{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the extended configuration ConfigMap name. +*/}} +{{- define "postgresql.extendedConfigurationCM" -}} +{{- if .Values.extendedConfConfigMap -}} +{{- printf "%s" (tpl .Values.extendedConfConfigMap $) -}} +{{- else -}} +{{- printf "%s-extended-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap should be mounted with PostgreSQL configuration +*/}} +{{- define "postgresql.mountConfigurationCM" -}} +{{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts ConfigMap name. +*/}} +{{- define "postgresql.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} +{{- printf "%s" (tpl .Values.initdbScriptsConfigMap $) -}} +{{- else -}} +{{- printf "%s-init-scripts" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts Secret name. +*/}} +{{- define "postgresql.initdbScriptsSecret" -}} +{{- printf "%s" (tpl .Values.initdbScriptsSecret $) -}} +{{- end -}} + +{{/* +Get the metrics ConfigMap name. +*/}} +{{- define "postgresql.metricsCM" -}} +{{- printf "%s-metrics" (include "common.names.fullname" .) -}} +{{- end -}} + +{{/* +Get the readiness probe command +*/}} +{{- define "postgresql.readinessProbeCommand" -}} +- | +{{- if (include "postgresql.database" .) }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} +{{- else }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} +{{- end }} +{{- if contains "bitnami/" .Values.image.repository }} + [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "postgresql.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "postgresql.validateValues.ldapConfigurationMethod" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.psp" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.tls" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If ldap.url is used then you don't need the other settings for ldap +*/}} +{{- define "postgresql.validateValues.ldapConfigurationMethod" -}} +{{- if and .Values.ldap.enabled (and (not (empty .Values.ldap.url)) (not (empty .Values.ldap.server))) }} +postgresql: ldap.url, ldap.server + You cannot set both `ldap.url` and `ldap.server` at the same time. + Please provide a unique way to configure LDAP. + More info at https://www.postgresql.org/docs/current/auth-ldap.html +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If PSP is enabled RBAC should be enabled too +*/}} +{{- define "postgresql.validateValues.psp" -}} +{{- if and .Values.psp.create (not .Values.rbac.create) }} +postgresql: psp.create, rbac.create + RBAC should be enabled if PSP is enabled in order for PSP to work. + More info at https://kubernetes.io/docs/concepts/policy/pod-security-policy/#authorizing-policies +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for podsecuritypolicy. +*/}} +{{- define "podsecuritypolicy.apiVersion" -}} +{{- if semverCompare "<1.10-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "policy/v1beta1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "postgresql.networkPolicy.apiVersion" -}} +{{- if semverCompare ">=1.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}} +"extensions/v1beta1" +{{- else if semverCompare "^1.7-0" .Capabilities.KubeVersion.GitVersion -}} +"networking.k8s.io/v1" +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql TLS - When TLS is enabled, so must be VolumePermissions +*/}} +{{- define "postgresql.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.volumePermissions.enabled) }} +postgresql: tls.enabled, volumePermissions.enabled + When TLS is enabled you must enable volumePermissions as well to ensure certificates files have + the right permissions. +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "postgresql.tlsCert" -}} +{{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "postgresql.tlsCertKey" -}} +{{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "postgresql.tlsCACert" -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.certCAFilename -}} +{{- end -}} + +{{/* +Return the path to the CRL file. +*/}} +{{- define "postgresql.tlsCRL" -}} +{{- if .Values.tls.crlFilename -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.crlFilename -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/configmap.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/configmap.yaml new file mode 100644 index 0000000000..3a5ea18ae9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/configmap.yaml @@ -0,0 +1,31 @@ +{{ if and (or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration) (not .Values.configurationConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-configuration + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: +{{- if (.Files.Glob "files/postgresql.conf") }} +{{ (.Files.Glob "files/postgresql.conf").AsConfig | indent 2 }} +{{- else if .Values.postgresqlConfiguration }} + postgresql.conf: | +{{- range $key, $value := default dict .Values.postgresqlConfiguration }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- if (.Files.Glob "files/pg_hba.conf") }} +{{ (.Files.Glob "files/pg_hba.conf").AsConfig | indent 2 }} +{{- else if .Values.pgHbaConfiguration }} + pg_hba.conf: | +{{ .Values.pgHbaConfiguration | indent 4 }} +{{- end }} +{{ end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml new file mode 100644 index 0000000000..b0dad253b5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml @@ -0,0 +1,26 @@ +{{- if and (or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf) (not .Values.extendedConfConfigMap)}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-extended-configuration + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: +{{- with .Files.Glob "files/conf.d/*.conf" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{ with .Values.postgresqlExtendedConf }} + override.conf: | +{{- range $key, $value := . }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extra-list.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extra-list.yaml new file mode 100644 index 0000000000..9ac65f9e16 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml new file mode 100644 index 0000000000..7796c67a93 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml @@ -0,0 +1,25 @@ +{{- if and (or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScripts) (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-init-scripts + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.sql.gz" }} +binaryData: +{{- range $path, $bytes := . }} + {{ base $path }}: {{ $.Files.Get $path | b64enc | quote }} +{{- end }} +{{- end }} +data: +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql}" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{- with .Values.initdbScripts }} +{{ toYaml . | indent 2 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml new file mode 100644 index 0000000000..fa539582bb --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "postgresql.metricsCM" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +data: + custom-metrics.yaml: {{ toYaml .Values.metrics.customMetrics | quote }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml new file mode 100644 index 0000000000..af8b67e2ff --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml @@ -0,0 +1,26 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-metrics + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- toYaml .Values.metrics.service.annotations | nindent 4 }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ .Values.metrics.service.type }} + {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{- end }} + ports: + - name: http-metrics + port: 9187 + targetPort: http-metrics + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml new file mode 100644 index 0000000000..4f2740ea0c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml @@ -0,0 +1,39 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "postgresql.networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + ingress: + # Allow inbound connections + - ports: + - port: {{ template "postgresql.port" . }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.explicitNamespacesSelector }} + namespaceSelector: +{{ toYaml .Values.networkPolicy.explicitNamespacesSelector | indent 12 }} + {{- end }} + - podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 14 }} + role: read + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes + - ports: + - port: 9187 + {{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000000..0c49694fad --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml @@ -0,0 +1,38 @@ +{{- if .Values.psp.create }} +apiVersion: {{ include "podsecuritypolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + privileged: false + volumes: + - 'configMap' + - 'secret' + - 'persistentVolumeClaim' + - 'emptyDir' + - 'projected' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml new file mode 100644 index 0000000000..d0f408c78f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} +{{- with .Values.metrics.prometheusRule.namespace }} + namespace: {{ . }} +{{- end }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- with .Values.metrics.prometheusRule.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: +{{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "postgresql.name" $ }} + rules: {{ tpl (toYaml .) $ | nindent 8 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/role.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/role.yaml new file mode 100644 index 0000000000..017a5716b1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/role.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +kind: Role +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +rules: + {{- if .Values.psp.create }} + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + verbs: ["use"] + resourceNames: + - {{ template "common.names.fullname" . }} + {{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/rolebinding.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/rolebinding.yaml new file mode 100644 index 0000000000..189775a15a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/rolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ template "common.names.fullname" . }} + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/secrets.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/secrets.yaml new file mode 100644 index 0000000000..d492cd593b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/secrets.yaml @@ -0,0 +1,24 @@ +{{- if (include "postgresql.createSecret" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + {{- if not (eq (include "postgresql.username" .) "postgres") }} + postgresql-postgres-password: {{ include "postgresql.postgres.password" . | b64enc | quote }} + {{- end }} + postgresql-password: {{ include "postgresql.password" . | b64enc | quote }} + {{- if .Values.replication.enabled }} + postgresql-replication-password: {{ include "postgresql.replication.password" . | b64enc | quote }} + {{- end }} + {{- if (and .Values.ldap.enabled .Values.ldap.bind_password)}} + postgresql-ldap-password: {{ .Values.ldap.bind_password | b64enc | quote }} + {{- end }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml new file mode 100644 index 0000000000..03f0f50e7d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if and (.Values.serviceAccount.enabled) (not .Values.serviceAccount.name) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "common.labels.standard" . | nindent 4 }} + name: {{ template "common.names.fullname" . }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml new file mode 100644 index 0000000000..587ce85b87 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "common.names.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- end }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.metrics.serviceMonitor.additionalLabels }} + {{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + +spec: + endpoints: + - port: http-metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml new file mode 100644 index 0000000000..b038299bf6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml @@ -0,0 +1,411 @@ +{{- if .Values.replication.enabled }} +{{- $readReplicasResources := coalesce .Values.readReplicas.resources .Values.resources -}} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: "{{ template "common.names.fullname" . }}-read" + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: read +{{- with .Values.readReplicas.labels }} +{{ toYaml . | indent 4 }} +{{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.readReplicas.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: {{ .Values.replication.readReplicas }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: read + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: read + role: read +{{- with .Values.readReplicas.podLabels }} +{{ toYaml . | indent 8 }} +{{- end }} +{{- with .Values.readReplicas.podAnnotations }} + annotations: +{{ toYaml . | indent 8 }} +{{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.readReplicas.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAffinityPreset "component" "read" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAntiAffinityPreset "component" "read" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.readReplicas.nodeAffinityPreset.type "key" .Values.readReplicas.nodeAffinityPreset.key "values" .Values.readReplicas.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.readReplicas.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name}} + {{- end }} + {{- if or .Values.readReplicas.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + {{ if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.readReplicas.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.readReplicas.priorityClassName }} + priorityClassName: {{ .Values.readReplicas.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if $readReplicasResources }} + resources: {{- toYaml $readReplicasResources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + - name: POSTGRESQL_PORT_NUMBER + value: "{{ template "postgresql.port" . }}" + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + - name: POSTGRES_REPLICATION_MODE + value: "slave" + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + - name: POSTGRES_MASTER_HOST + value: {{ template "common.names.fullname" . }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ include "postgresql.port" . | quote }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ template "postgresql.port" . }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{ end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.readReplicas.extraVolumeMounts }} + {{- toYaml .Values.readReplicas.extraVolumeMounts | nindent 12 }} + {{- end }} +{{- if .Values.readReplicas.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.sidecars "context" $ ) | nindent 8 }} +{{- end }} + volumes: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi + {{- end }} + {{- if or (not .Values.persistence.enabled) (not .Values.readReplicas.persistence.enabled) }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.readReplicas.extraVolumes }} + {{- toYaml .Values.readReplicas.extraVolumes | nindent 8 }} + {{- end }} + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} +{{- if and .Values.persistence.enabled .Values.readReplicas.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 0000000000..f8163fd99f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/statefulset.yaml @@ -0,0 +1,609 @@ +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ template "postgresql.primary.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- with .Values.primary.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.primary.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: 1 + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: primary + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + role: primary + app.kubernetes.io/component: primary + {{- with .Values.primary.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.primary.podAnnotations }} + annotations: {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.primary.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.primary.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAffinityPreset "component" "primary" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAntiAffinityPreset "component" "primary" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.primary.nodeAffinityPreset.type "key" .Values.primary.nodeAffinityPreset.key "values" .Values.primary.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.primary.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.primary.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.primary.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.primary.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + {{- end }} + {{- if or .Values.primary.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.primary.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.primary.priorityClassName }} + priorityClassName: {{ .Values.primary.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: POSTGRESQL_PORT_NUMBER + value: "{{ template "postgresql.port" . }}" + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + {{- if .Values.postgresqlInitdbArgs }} + - name: POSTGRES_INITDB_ARGS + value: {{ .Values.postgresqlInitdbArgs | quote }} + {{- end }} + {{- if .Values.postgresqlInitdbWalDir }} + - name: POSTGRES_INITDB_WALDIR + value: {{ .Values.postgresqlInitdbWalDir | quote }} + {{- end }} + {{- if .Values.initdbUser }} + - name: POSTGRESQL_INITSCRIPTS_USERNAME + value: {{ .Values.initdbUser }} + {{- end }} + {{- if .Values.initdbPassword }} + - name: POSTGRESQL_INITSCRIPTS_PASSWORD + value: {{ .Values.initdbPassword }} + {{- end }} + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + {{- if .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_MASTER_HOST + value: {{ .Values.primaryAsStandBy.primaryHost }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ .Values.primaryAsStandBy.primaryPort | quote }} + {{- end }} + {{- if or .Values.replication.enabled .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_REPLICATION_MODE + {{- if .Values.primaryAsStandBy.enabled }} + value: "slave" + {{- else }} + value: "master" + {{- end }} + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + {{- if not (eq .Values.replication.synchronousCommit "off")}} + - name: POSTGRES_SYNCHRONOUS_COMMIT_MODE + value: {{ .Values.replication.synchronousCommit | quote }} + - name: POSTGRES_NUM_SYNCHRONOUS_REPLICAS + value: {{ .Values.replication.numSynchronousReplicas | quote }} + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + {{- end }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + - name: POSTGRES_USER + value: {{ include "postgresql.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + {{- if (include "postgresql.database" .) }} + - name: POSTGRES_DB + value: {{ (include "postgresql.database" .) | quote }} + {{- end }} + {{- if .Values.extraEnv }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnv "context" $) | nindent 12 }} + {{- end }} + - name: POSTGRESQL_ENABLE_LDAP + value: {{ ternary "yes" "no" .Values.ldap.enabled | quote }} + {{- if .Values.ldap.enabled }} + - name: POSTGRESQL_LDAP_SERVER + value: {{ .Values.ldap.server }} + - name: POSTGRESQL_LDAP_PORT + value: {{ .Values.ldap.port | quote }} + - name: POSTGRESQL_LDAP_SCHEME + value: {{ .Values.ldap.scheme }} + {{- if .Values.ldap.tls }} + - name: POSTGRESQL_LDAP_TLS + value: "1" + {{- end }} + - name: POSTGRESQL_LDAP_PREFIX + value: {{ .Values.ldap.prefix | quote }} + - name: POSTGRESQL_LDAP_SUFFIX + value: {{ .Values.ldap.suffix | quote }} + - name: POSTGRESQL_LDAP_BASE_DN + value: {{ .Values.ldap.baseDN }} + - name: POSTGRESQL_LDAP_BIND_DN + value: {{ .Values.ldap.bindDN }} + {{- if (not (empty .Values.ldap.bind_password)) }} + - name: POSTGRESQL_LDAP_BIND_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-ldap-password + {{- end}} + - name: POSTGRESQL_LDAP_SEARCH_ATTR + value: {{ .Values.ldap.search_attr }} + - name: POSTGRESQL_LDAP_SEARCH_FILTER + value: {{ .Values.ldap.search_filter }} + - name: POSTGRESQL_LDAP_URL + value: {{ .Values.ldap.url }} + {{- end}} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + {{- if .Values.extraEnvVarsCM }} + envFrom: + - configMapRef: + name: {{ tpl .Values.extraEnvVarsCM . }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ template "postgresql.port" . }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + {{- else if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ template "postgresql.port" . }} + {{- end }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d/ + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + mountPath: /docker-entrypoint-initdb.d/secret + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.primary.extraVolumeMounts }} + {{- toYaml .Values.primary.extraVolumeMounts | nindent 12 }} + {{- end }} +{{- if .Values.primary.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.primary.sidecars "context" $ ) | nindent 8 }} +{{- end }} +{{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "postgresql.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.securityContext.enabled }} + securityContext: {{- omit .Values.metrics.securityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + env: + {{- $database := required "In order to enable metrics you need to specify a database (.Values.postgresqlDatabase or .Values.global.postgresql.postgresqlDatabase)" (include "postgresql.database" .) }} + {{- $sslmode := ternary "require" "disable" .Values.tls.enabled }} + {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} + - name: DATA_SOURCE_NAME + value: {{ printf "host=127.0.0.1 port=%d user=%s sslmode=%s sslcert=%s sslkey=%s" (int (include "postgresql.port" .)) (include "postgresql.username" .) $sslmode (include "postgresql.tlsCert" .) (include "postgresql.tlsCertKey" .) }} + {{- else }} + - name: DATA_SOURCE_URI + value: {{ printf "127.0.0.1:%d/%s?sslmode=%s" (int (include "postgresql.port" .)) $database $sslmode }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: DATA_SOURCE_PASS_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: DATA_SOURCE_PASS + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: DATA_SOURCE_USER + value: {{ template "postgresql.username" . }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.readinessProbe.failureThreshold }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.customMetrics }} + - name: custom-metrics + mountPath: /conf + readOnly: true + args: ["--extend.query-path", "/conf/custom-metrics.yaml"] + {{- end }} + ports: + - name: http-metrics + containerPort: 9187 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} +{{- end }} + volumes: + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ template "postgresql.initdbScriptsCM" . }} + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + secret: + secretName: {{ template "postgresql.initdbScriptsSecret" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.primary.extraVolumes }} + {{- toYaml .Values.primary.extraVolumes | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} + - name: custom-metrics + configMap: + name: {{ template "postgresql.metricsCM" . }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi + {{- end }} +{{- if and .Values.persistence.enabled .Values.persistence.existingClaim }} + - name: data + persistentVolumeClaim: +{{- with .Values.persistence.existingClaim }} + claimName: {{ tpl . $ }} +{{- end }} +{{- else if not .Values.persistence.enabled }} + - name: data + emptyDir: {} +{{- else if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-headless.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-headless.yaml new file mode 100644 index 0000000000..6f5f3b9ee4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-headless.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-headless + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + # Use this annotation in addition to the actual publishNotReadyAddresses + # field below because the annotation will stop being respected soon but the + # field is broken in some versions of Kubernetes: + # https://github.com/kubernetes/kubernetes/issues/58662 + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + clusterIP: None + # We want all pods in the StatefulSet to have their addresses published for + # the sake of the other Postgresql pods even before they're ready, since they + # have to be able to talk to each other in order to become ready. + publishNotReadyAddresses: true + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-read.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-read.yaml new file mode 100644 index 0000000000..56195ea1e6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc-read.yaml @@ -0,0 +1,43 @@ +{{- if .Values.replication.enabled }} +{{- $serviceAnnotations := coalesce .Values.readReplicas.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.readReplicas.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.readReplicas.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.readReplicas.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.readReplicas.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.readReplicas.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-read + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: read +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc.yaml new file mode 100644 index 0000000000..a29431b6a4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/templates/svc.yaml @@ -0,0 +1,41 @@ +{{- $serviceAnnotations := coalesce .Values.primary.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.primary.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.primary.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.primary.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.primary.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.primary.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.port" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.schema.json b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.schema.json new file mode 100644 index 0000000000..66a2a9dd06 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "postgresqlUsername": { + "type": "string", + "title": "Admin user", + "form": true + }, + "postgresqlPassword": { + "type": "string", + "title": "Password", + "form": true + }, + "persistence": { + "type": "object", + "properties": { + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi" + } + } + }, + "resources": { + "type": "object", + "title": "Required Resources", + "description": "Configure resource requests", + "form": true, + "properties": { + "requests": { + "type": "object", + "properties": { + "memory": { + "type": "string", + "form": true, + "render": "slider", + "title": "Memory Request", + "sliderMin": 10, + "sliderMax": 2048, + "sliderUnit": "Mi" + }, + "cpu": { + "type": "string", + "form": true, + "render": "slider", + "title": "CPU Request", + "sliderMin": 10, + "sliderMax": 2000, + "sliderUnit": "m" + } + } + } + } + }, + "replication": { + "type": "object", + "form": true, + "title": "Replication Details", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable Replication", + "form": true + }, + "readReplicas": { + "type": "integer", + "title": "read Replicas", + "form": true, + "hidden": { + "value": false, + "path": "replication/enabled" + } + } + } + }, + "volumePermissions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable Init Containers", + "description": "Change the owner of the persist volume mountpoint to RunAsUser:fsGroup" + } + } + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Configure metrics exporter", + "form": true + } + } + } + } +} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.yaml new file mode 100644 index 0000000000..82ce092344 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/charts/postgresql/values.yaml @@ -0,0 +1,824 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +global: + postgresql: {} +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass + +## Bitnami PostgreSQL image version +## ref: https://hub.docker.com/r/bitnami/postgresql/tags/ +## +image: + registry: docker.io + repository: bitnami/postgresql + tag: 11.11.0-debian-10-r71 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Set to true if you would like to see extra information on logs + ## It turns BASH and/or NAMI debugging in the image + ## + debug: false + +## String to partially override common.names.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override common.names.fullname template +## +# fullnameOverride: + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: "10" + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + ## Init container Security Context + ## Note: the chown of the data folder is done to securityContext.runAsUser + ## and not the below volumePermissions.securityContext.runAsUser + ## When runAsUser is set to special value "auto", init container will try to chwon the + ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). + ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with + ## pod securityContext.enabled=false and shmVolume.chmod.enabled=false + ## + securityContext: + runAsUser: 0 + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## Pod Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## +securityContext: + enabled: true + fsGroup: 1001 + +## Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + +## Pod Service Account +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + enabled: false + ## Name of an already existing service account. Setting this value disables the automatic service account creation. + # name: + +## Pod Security Policy +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +psp: + create: false + +## Creates role for ServiceAccount +## Required for PSP +## +rbac: + create: false + +replication: + enabled: false + user: repl_user + password: repl_password + readReplicas: 1 + ## Set synchronous commit mode: on, off, remote_apply, remote_write and local + ## ref: https://www.postgresql.org/docs/9.6/runtime-config-wal.html#GUC-WAL-LEVEL + synchronousCommit: 'off' + ## From the number of `readReplicas` defined above, set the number of those that will have synchronous replication + ## NOTE: It cannot be > readReplicas + numSynchronousReplicas: 0 + ## Replication Cluster application name. Useful for defining multiple replication policies + ## + applicationName: my_application + +## PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-user-on-first-run (see note!) +# postgresqlPostgresPassword: + +## PostgreSQL user (has superuser privileges if username is `postgres`) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +postgresqlUsername: postgres + +## PostgreSQL password +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +# postgresqlPassword: + +## PostgreSQL password using existing secret +## existingSecret: secret +## + +## Mount PostgreSQL secret as a file instead of passing environment variable +# usePasswordFile: false + +## Create a database +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-on-first-run +## +# postgresqlDatabase: + +## PostgreSQL data dir +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +postgresqlDataDir: /bitnami/postgresql/data + +## An array to add extra environment variables +## For example: +## extraEnv: +## - name: FOO +## value: "bar" +## +# extraEnv: +extraEnv: [] + +## Name of a ConfigMap containing extra env vars +## +# extraEnvVarsCM: + +## Specify extra initdb args +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +# postgresqlInitdbArgs: + +## Specify a custom location for the PostgreSQL transaction log +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +# postgresqlInitdbWalDir: + +## PostgreSQL configuration +## Specify runtime configuration parameters as a dict, using camelCase, e.g. +## {"sharedBuffers": "500MB"} +## Alternatively, you can put your postgresql.conf under the files/ directory +## ref: https://www.postgresql.org/docs/current/static/runtime-config.html +## +# postgresqlConfiguration: + +## PostgreSQL extended configuration +## As above, but _appended_ to the main configuration +## Alternatively, you can put your *.conf under the files/conf.d/ directory +## https://github.com/bitnami/bitnami-docker-postgresql#allow-settings-to-be-loaded-from-files-other-than-the-default-postgresqlconf +## +# postgresqlExtendedConf: + +## Configure current cluster's primary server to be the standby server in other cluster. +## This will allow cross cluster replication and provide cross cluster high availability. +## You will need to configure pgHbaConfiguration if you want to enable this feature with local cluster replication enabled. +## +primaryAsStandBy: + enabled: false + # primaryHost: + # primaryPort: + +## PostgreSQL client authentication configuration +## Specify content for pg_hba.conf +## Default: do not create pg_hba.conf +## Alternatively, you can put your pg_hba.conf under the files/ directory +# pgHbaConfiguration: |- +# local all all trust +# host all all localhost trust +# host mydatabase mysuser 192.168.0.0/24 md5 + +## ConfigMap with PostgreSQL configuration +## NOTE: This will override postgresqlConfiguration and pgHbaConfiguration +# configurationConfigMap: + +## ConfigMap with PostgreSQL extended configuration +# extendedConfConfigMap: + +## initdb scripts +## Specify dictionary of scripts to be run at first boot +## Alternatively, you can put your scripts under the files/docker-entrypoint-initdb.d directory +## +# initdbScripts: +# my_init_script.sh: | +# #!/bin/sh +# echo "Do something." + +## ConfigMap with scripts to be run at first boot +## NOTE: This will override initdbScripts +# initdbScriptsConfigMap: + +## Secret with scripts to be run at first boot (in case it contains sensitive information) +## NOTE: This can work along initdbScripts or initdbScriptsConfigMap +# initdbScriptsSecret: + +## Specify the PostgreSQL username and password to execute the initdb scripts +# initdbUser: +# initdbPassword: + +## Audit settings +## https://github.com/bitnami/bitnami-docker-postgresql#auditing +## +audit: + ## Log client hostnames + ## + logHostname: false + ## Log connections to the server + ## + logConnections: false + ## Log disconnections + ## + logDisconnections: false + ## Operation to audit using pgAudit (default if not set) + ## + pgAuditLog: "" + ## Log catalog using pgAudit + ## + pgAuditLogCatalog: "off" + ## Log level for clients + ## + clientMinMessages: error + ## Template for log line prefix (default if not set) + ## + logLinePrefix: "" + ## Log timezone + ## + logTimezone: "" + +## Shared preload libraries +## +postgresqlSharedPreloadLibraries: "pgaudit" + +## Maximum total connections +## +postgresqlMaxConnections: + +## Maximum connections for the postgres user +## +postgresqlPostgresConnectionLimit: + +## Maximum connections for the created user +## +postgresqlDbUserConnectionLimit: + +## TCP keepalives interval +## +postgresqlTcpKeepalivesInterval: + +## TCP keepalives idle +## +postgresqlTcpKeepalivesIdle: + +## TCP keepalives count +## +postgresqlTcpKeepalivesCount: + +## Statement timeout +## +postgresqlStatementTimeout: + +## Remove pg_hba.conf lines with the following comma-separated patterns +## (cannot be used with custom pg_hba.conf) +## +postgresqlPghbaRemoveFilters: + +## Optional duration in seconds the pod needs to terminate gracefully. +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods +## +# terminationGracePeriodSeconds: 30 + +## LDAP configuration +## +ldap: + enabled: false + url: '' + server: '' + port: '' + prefix: '' + suffix: '' + baseDN: '' + bindDN: '' + bind_password: + search_attr: '' + search_filter: '' + scheme: '' + tls: {} + +## PostgreSQL service configuration +## +service: + ## PosgresSQL service type + ## + type: ClusterIP + # clusterIP: None + port: 5432 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. Evaluated as a template. + ## + annotations: {} + ## Set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + # loadBalancerIP: + ## Load Balancer sources. Evaluated as a template. + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## + # loadBalancerSourceRanges: + # - 10.10.10.0/24 + +## Start primary and read(s) pod(s) without limitations on shm memory. +## By default docker and containerd (and possibly other container runtimes) +## limit `/dev/shm` to `64M` (see e.g. the +## [docker issue](https://github.com/docker-library/postgres/issues/416) and the +## [containerd issue](https://github.com/containerd/containerd/issues/3654), +## which could be not enough if PostgreSQL uses parallel workers heavily. +## +shmVolume: + ## Set `shmVolume.enabled` to `true` to mount a new tmpfs volume to remove + ## this limitation. + ## + enabled: true + ## Set to `true` to `chmod 777 /dev/shm` on a initContainer. + ## This option is ignored if `volumePermissions.enabled` is `false` + ## + chmod: + enabled: true + +## PostgreSQL data Persistent Volume Storage Class +## If defined, storageClassName: +## If set to "-", storageClassName: "", which disables dynamic provisioning +## If undefined (the default) or set to null, no storageClassName spec is +## set, choosing the default provisioner. (gp2 on AWS, standard on +## GKE, AWS & OpenStack) +## +persistence: + enabled: true + ## A manually managed Persistent Volume and Claim + ## If defined, PVC must be created manually before volume will be bound + ## The value is evaluated as a template, so, for example, the name can depend on .Release or .Chart + ## + # existingClaim: + + ## The path the volume will be mounted at, useful when using different + ## PostgreSQL images. + ## + mountPath: /bitnami/postgresql + + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + ## + subPath: '' + + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + annotations: {} + ## selector can be used to match an existing PersistentVolume + ## selector: + ## matchLabels: + ## app: my-app + selector: {} + +## updateStrategy for PostgreSQL StatefulSet and its reads StatefulSets +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate + +## +## PostgreSQL Primary parameters +## +primary: + ## PostgreSQL Primary pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## PostgreSQL Primary pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## PostgreSQL Primary node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: primary.podAffinityPreset, primary.podAntiAffinityPreset, and primary.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + + labels: {} + annotations: {} + podLabels: {} + podAnnotations: {} + priorityClassName: '' + ## Extra init containers + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + + ## Additional PostgreSQL primary Volume mounts + ## + extraVolumeMounts: [] + ## Additional PostgreSQL primary Volumes + ## + extraVolumes: [] + ## Add sidecars to the pod + ## + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + + ## Override the service configuration for primary + ## + service: {} + # type: + # nodePort: + # clusterIP: + +## +## PostgreSQL read only replica parameters +## +readReplicas: + ## PostgreSQL read only pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## PostgreSQL read only pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## PostgreSQL read only node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: readReplicas.podAffinityPreset, readReplicas.podAntiAffinityPreset, and readReplicas.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + labels: {} + annotations: {} + podLabels: {} + podAnnotations: {} + priorityClassName: '' + + ## Extra init containers + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + + ## Additional PostgreSQL read replicas Volume mounts + ## + extraVolumeMounts: [] + + ## Additional PostgreSQL read replicas Volumes + ## + extraVolumes: [] + + ## Add sidecars to the pod + ## + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + + ## Override the service configuration for read + ## + service: {} + # type: + # nodePort: + # clusterIP: + + ## Whether to enable PostgreSQL read replicas data Persistent + ## + persistence: + enabled: true + + # Override the resource configuration for read replicas + resources: {} + # requests: + # memory: 256Mi + # cpu: 250m + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + requests: + memory: 256Mi + cpu: 250m + +## Add annotations to all the deployed resources +## +commonAnnotations: {} + +networkPolicy: + ## Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. + ## + enabled: false + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port PostgreSQL is listening + ## on. When true, PostgreSQL will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + + ## if explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace + ## and that match other criteria, the ones that have the good label, can reach the DB. + ## But sometimes, we want the DB to be accessible to clients from other namespaces, in this case, we can use this + ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added. + ## + ## Example: + ## explicitNamespacesSelector: + ## matchLabels: + ## role: frontend + ## matchExpressions: + ## - {key: role, operator: In, values: [frontend]} + ## + explicitNamespacesSelector: {} + +## Configure extra options for startup, liveness and readiness probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 10 + successThreshold: 1 + +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Custom Startup probe +## +customStartupProbe: {} + +## Custom Liveness probe +## +customLivenessProbe: {} + +## Custom Rediness probe +## +customReadinessProbe: {} + +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to use the server's TLS cipher preferences rather than the client's. + preferServerCiphers: true + # + # Name of the Secret that contains the certificates + certificatesSecret: '' + # + # Certificate filename + certFilename: '' + # + # Certificate Key filename + certKeyFilename: '' + # + # CA Certificate filename + # If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate + # ref: https://www.postgresql.org/docs/9.6/auth-methods.html + certCAFilename: + # + # File containing a Certificate Revocation List + crlFilename: + +## Configure metrics exporter +## +metrics: + enabled: false + # resources: {} + service: + type: ClusterIP + annotations: + prometheus.io/scrape: 'true' + prometheus.io/port: '9187' + loadBalancerIP: + serviceMonitor: + enabled: false + additionalLabels: {} + # namespace: monitoring + # interval: 30s + # scrapeTimeout: 10s + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + namespace: '' + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current postgresql service. + ## rules: + ## - alert: HugeReplicationLag + ## expr: pg_replication_lag{service="{{ template "common.names.fullname" . }}-metrics"} / 3600 > 1 + ## for: 1m + ## labels: + ## severity: critical + ## annotations: + ## description: replication for {{ template "common.names.fullname" . }} PostgreSQL is lagging by {{ "{{ $value }}" }} hour(s). + ## summary: PostgreSQL replication is lagging by {{ "{{ $value }}" }} hour(s). + ## + rules: [] + + image: + registry: docker.io + repository: bitnami/postgres-exporter + tag: 0.9.0-debian-10-r43 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + ## Define additional custom metrics + ## ref: https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file + # customMetrics: + # pg_database: + # query: "SELECT d.datname AS name, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_database_size(d.datname) ELSE 0 END AS size_bytes FROM pg_catalog.pg_database d where datname not in ('template0', 'template1', 'postgres')" + # metrics: + # - name: + # usage: "LABEL" + # description: "Name of the database" + # - size_bytes: + # usage: "GAUGE" + # description: "Size of the database in bytes" + # + ## An array to add extra env vars to configure postgres-exporter + ## see: https://github.com/wrouesnel/postgres_exporter#environment-variables + ## For example: + # extraEnvVars: + # - name: PG_EXPORTER_DISABLE_DEFAULT_METRICS + # value: "true" + extraEnvVars: {} + + ## Pod Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## + securityContext: + enabled: false + runAsUser: 1001 + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## Configure extra options for liveness and readiness probes + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Array with extra yaml to deploy with the chart. Evaluated as a template +## +extraDeploy: [] diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/access-tls-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/access-tls-values.yaml new file mode 100644 index 0000000000..1a8c4698d2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/access-tls-values.yaml @@ -0,0 +1,24 @@ +databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" +access: + accessConfig: + security: + tls: true + resetAccessCAKeys: true diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/default-values.yaml new file mode 100644 index 0000000000..fc34693998 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/default-values.yaml @@ -0,0 +1,21 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/derby-test-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/derby-test-values.yaml new file mode 100644 index 0000000000..82ff485457 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/derby-test-values.yaml @@ -0,0 +1,19 @@ +databaseUpgradeReady: true + +postgresql: + enabled: false +artifactory: + podSecurityContext: + fsGroupChangePolicy: "OnRootMismatch" + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/global-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/global-values.yaml new file mode 100644 index 0000000000..33bbf04a20 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/global-values.yaml @@ -0,0 +1,247 @@ +databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + customInitContainersBegin: | + - name: "custom-init-begin-local" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in local" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: artifactory-volume + customInitContainers: | + - name: "custom-init-local" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in local" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: artifactory-volume + # Add custom volumes + customVolumes: | + - name: custom-script-local + emptyDir: + sizeLimit: 100Mi + # Add custom volumesMounts + customVolumeMounts: | + - name: custom-script-local + mountPath: "/scriptslocal" + # Add custom sidecar containers + customSidecarContainers: | + - name: "sidecar-list-local" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in local' >> /scriptslocal/sidecarlocal.txt; cat /scriptslocal/sidecarlocal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scriptslocal" + name: custom-script-local + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + +global: + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + customInitContainersBegin: | + - name: "custom-init-begin-global" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in global" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: artifactory-volume + customInitContainers: | + - name: "custom-init-global" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in global" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: artifactory-volume + # Add custom volumes + customVolumes: | + - name: custom-script-global + emptyDir: + sizeLimit: 100Mi + # Add custom volumesMounts + customVolumeMounts: | + - name: custom-script-global + mountPath: "/scripts" + # Add custom sidecar containers + customSidecarContainers: | + - name: "sidecar-list-global" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in global' >> /scripts/sidecarglobal.txt; cat /scripts/sidecarglobal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scripts" + name: custom-script-global + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + +nginx: + customInitContainers: | + - name: "custom-init-begin-nginx" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + command: + - 'sh' + - '-c' + - echo "running in nginx" + volumeMounts: + - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + name: custom-script-local + customSidecarContainers: | + - name: "sidecar-list-nginx" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - NET_RAW + command: ["sh","-c","echo 'Sidecar is running in local' >> /scriptslocal/sidecarlocal.txt; cat /scriptslocal/sidecarlocal.txt; while true; do sleep 30; done"] + volumeMounts: + - mountPath: "/scriptslocal" + name: custom-script-local + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + # Add custom volumes + customVolumes: | + - name: custom-script-local + emptyDir: + sizeLimit: 100Mi + + artifactoryConf: | + {{- if .Values.nginx.https.enabled }} + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_certificate {{ .Values.nginx.persistence.mountPath }}/ssl/tls.crt; + ssl_certificate_key {{ .Values.nginx.persistence.mountPath }}/ssl/tls.key; + ssl_session_cache shared:SSL:1m; + ssl_prefer_server_ciphers on; + {{- end }} + ## server configuration + server { + listen 8088; + {{- if .Values.nginx.internalPortHttps }} + listen {{ .Values.nginx.internalPortHttps }} ssl; + {{- else -}} + {{- if .Values.nginx.https.enabled }} + listen {{ .Values.nginx.https.internalPort }} ssl; + {{- end }} + {{- end }} + {{- if .Values.nginx.internalPortHttp }} + listen {{ .Values.nginx.internalPortHttp }}; + {{- else -}} + {{- if .Values.nginx.http.enabled }} + listen {{ .Values.nginx.http.internalPort }}; + {{- end }} + {{- end }} + server_name ~(?.+)\.{{ include "artifactory.fullname" . }} {{ include "artifactory.fullname" . }} + {{- range .Values.ingress.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} + {{- end -}}; + + if ($http_x_forwarded_proto = '') { + set $http_x_forwarded_proto $scheme; + } + ## Application specific logs + ## access_log /var/log/nginx/artifactory-access.log timing; + ## error_log /var/log/nginx/artifactory-error.log; + rewrite ^/artifactory/?$ / redirect; + if ( $repo != "" ) { + rewrite ^/(v1|v2)/(.*) /artifactory/api/docker/$repo/$1/$2 break; + } + chunked_transfer_encoding on; + client_max_body_size 0; + + location / { + proxy_read_timeout 900; + proxy_pass_header Server; + proxy_cookie_path ~*^/.* /; + proxy_pass {{ include "artifactory.scheme" . }}://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalPort }}/; + {{- if .Values.nginx.service.ssloffload}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$server_port; + proxy_set_header X-Forwarded-Port $server_port; + {{- end }} + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location /artifactory/ { + if ( $request_uri ~ ^/artifactory/(.*)$ ) { + proxy_pass {{ include "artifactory.scheme" . }}://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/$1; + } + proxy_pass {{ include "artifactory.scheme" . }}://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/; + } + } + } + + ## A list of custom ports to expose on the NGINX pod. Follows the conventional Kubernetes yaml syntax for container ports. + customPorts: + - containerPort: 8088 + name: http2 + service: + ## A list of custom ports to expose through the Ingress controller service. Follows the conventional Kubernetes yaml syntax for service ports. + customPorts: + - port: 8088 + targetPort: 8088 + protocol: TCP + name: http2 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/large-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/large-values.yaml new file mode 100644 index 0000000000..94a485d6f4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/large-values.yaml @@ -0,0 +1,82 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + database: + maxOpenConnections: 150 + tomcat: + connector: + maxThreads: 300 + resources: + requests: + memory: "6Gi" + cpu: "2" + limits: + memory: "10Gi" + cpu: "8" + javaOpts: + xms: "8g" + xmx: "10g" +access: + database: + maxOpenConnections: 150 + tomcat: + connector: + maxThreads: 100 +router: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 150 + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/loggers-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/loggers-values.yaml new file mode 100644 index 0000000000..03c94be953 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/loggers-values.yaml @@ -0,0 +1,43 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + + loggers: + - access-audit.log + - access-request.log + - access-security-audit.log + - access-service.log + - artifactory-access.log + - artifactory-event.log + - artifactory-import-export.log + - artifactory-request.log + - artifactory-service.log + - frontend-request.log + - frontend-service.log + - metadata-request.log + - metadata-service.log + - router-request.log + - router-service.log + - router-traefik.log + + catalinaLoggers: + - tomcat-catalina.log + - tomcat-localhost.log diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/medium-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/medium-values.yaml new file mode 100644 index 0000000000..35044dc36d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/medium-values.yaml @@ -0,0 +1,82 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + database: + maxOpenConnections: 100 + tomcat: + connector: + maxThreads: 200 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "8Gi" + cpu: "6" + javaOpts: + xms: "6g" + xmx: "8g" +access: + database: + maxOpenConnections: 100 + tomcat: + connector: + maxThreads: 50 +router: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 100 + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "200Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/migration-disabled-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/migration-disabled-values.yaml new file mode 100644 index 0000000000..092756fb65 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/migration-disabled-values.yaml @@ -0,0 +1,21 @@ +databaseUpgradeReady: true +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + migration: + enabled: false + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/nginx-autoreload-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/nginx-autoreload-values.yaml new file mode 100644 index 0000000000..09616c5bf4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/nginx-autoreload-values.yaml @@ -0,0 +1,42 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + +nginx: + customVolumes: | + - name: scripts + configMap: + name: {{ template "artifactory.fullname" . }}-nginx-scripts + defaultMode: 0550 + customVolumeMounts: | + - name: scripts + mountPath: /var/opt/jfrog/nginx/scripts/ + customCommand: + - /bin/sh + - -c + - | + # watch for configmap changes + /sbin/inotifyd /var/opt/jfrog/nginx/scripts/configreloader.sh {{ .Values.nginx.persistence.mountPath -}}/conf.d:n & + {{ if .Values.nginx.https.enabled -}} + # watch for tls secret changes + /sbin/inotifyd /var/opt/jfrog/nginx/scripts/configreloader.sh {{ .Values.nginx.persistence.mountPath -}}/ssl:n & + {{ end -}} + nginx -g 'daemon off;' diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml new file mode 100644 index 0000000000..a38969a8ff --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml @@ -0,0 +1,96 @@ +databaseUpgradeReady: true +artifactory: + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + +access: + accessConfig: + security: + tls: true + resetAccessCAKeys: true + +postgresql: + postgresqlPassword: password + postgresqlExtendedConf: + maxConnections: 102 + persistence: + enabled: false + +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true + +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false +jfconnect: + enabled: true + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +mc: + enabled: true +splitServicesToContainers: true + +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values.yaml new file mode 100644 index 0000000000..057ae9bf36 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/rtsplit-values.yaml @@ -0,0 +1,151 @@ +databaseUpgradeReady: true +artifactory: + replicaCount: 1 + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + + # Add lifecycle hooks for artifactory container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the artifactory postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the artifactory postStart handler >> /tmp/message"] + +postgresql: + postgresqlPassword: password + postgresqlExtendedConf: + maxConnections: 100 + persistence: + enabled: false + +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true + +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false +jfconnect: + enabled: true + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for jfconect container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the jfconnect postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the jfconnect postStart handler >> /tmp/message"] + + +mc: + enabled: true +splitServicesToContainers: true + +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for router container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the router postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the router postStart handler >> /tmp/message"] + +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + # Add lifecycle hooks for frontend container + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the frontend postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the frontend postStart handler >> /tmp/message"] + +metadata: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the metadata postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the metadata postStart handler >> /tmp/message"] + +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the event postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the event postStart handler >> /tmp/message"] + +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" + lifecycle: + postStart: + exec: + command: ["/bin/sh", "-c", "echo Hello from the observability postStart handler >> /tmp/message"] + preStop: + exec: + command: ["/bin/sh", "-c", "echo Hello from the observability postStart handler >> /tmp/message"] diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/small-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/small-values.yaml new file mode 100644 index 0000000000..70d77790ab --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/small-values.yaml @@ -0,0 +1,82 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +databaseUpgradeReady: true + +# To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release +postgresql: + postgresqlPassword: password + persistence: + enabled: false +artifactory: + persistence: + enabled: false + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 200 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "6g" +access: + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 50 +router: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +frontend: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +metadata: + database: + maxOpenConnections: 80 + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +event: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +jfconnect: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" +observability: + resources: + requests: + memory: "100Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/test-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/test-values.yaml new file mode 100644 index 0000000000..d2beb0eff6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/ci/test-values.yaml @@ -0,0 +1,84 @@ +databaseUpgradeReady: true +artifactory: + replicaCount: 3 + joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + unifiedSecretInstallation: false + metrics: + enabled: true + persistence: + enabled: false + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "6Gi" + cpu: "4" + javaOpts: + xms: "4g" + xmx: "4g" + statefulset: + annotations: + artifactory: test + +postgresql: + postgresqlPassword: password + postgresqlExtendedConf: + maxConnections: 100 + persistence: + enabled: false + +rbac: + create: true +serviceAccount: + create: true + automountServiceAccountToken: true + +ingress: + enabled: true + className: "testclass" + hosts: + - demonow.xyz +nginx: + enabled: false + +jfconnect: + enabled: false + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 70 + +## filebeat sidecar +filebeat: + enabled: true + filebeatYml: | + logging.level: info + path.data: {{ .Values.artifactory.persistence.mountPath }}/log/filebeat + name: artifactory-filebeat + queue.spool: + file: + permissions: 0760 + filebeat.inputs: + - type: log + enabled: true + close_eof: ${CLOSE:false} + paths: + - {{ .Values.artifactory.persistence.mountPath }}/log/*.log + fields: + service: "jfrt" + log_type: "artifactory" + output.file: + path: "/tmp/filebeat" + filename: filebeat + readinessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + curl --fail 127.0.0.1:5066 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/binarystore.xml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/binarystore.xml new file mode 100644 index 0000000000..e396e0a418 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/binarystore.xml @@ -0,0 +1,426 @@ +{{- if eq .Values.artifactory.persistence.type "nfs" -}} + + {{- if (.Values.artifactory.persistence.maxCacheSize) }} + + + + + + {{- else }} + + + + {{- end }} + + {{- if .Values.artifactory.persistence.maxCacheSize }} + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + + + {{ .Values.artifactory.persistence.nfs.dataDir }}/filestore + + +{{- end }} +{{- if eq .Values.artifactory.persistence.type "file-system" -}} + + + + {{- if .Values.artifactory.persistence.fileSystem.cache.enabled }} + + {{- end }} + + {{- if .Values.artifactory.persistence.fileSystem.cache.enabled }} + + {{- end }} + + + {{- if .Values.artifactory.persistence.fileSystem.cache.enabled }} + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + +{{- end }} +{{- if eq .Values.artifactory.persistence.type "cluster-file-system" -}} + + + + + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + {{ .Values.artifactory.persistence.lenientLimit }} + 2 + + + + + + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + + + shard-fs-1 + local + + + + + 30 + tester-remote1 + 10000 + remote + + + +{{- end }} +{{- if or (eq .Values.artifactory.persistence.type "google-storage") (eq .Values.artifactory.persistence.type "google-storage-v2") (eq .Values.artifactory.persistence.type "cluster-google-storage-v2") (eq .Values.artifactory.persistence.type "google-storage-v2-direct") }} + + + {{- if or (eq .Values.artifactory.persistence.type "google-storage") (eq .Values.artifactory.persistence.type "google-storage-v2") }} + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "cluster-google-storage-v2" }} + + + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + {{ .Values.artifactory.persistence.lenientLimit }} + 2 + + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "google-storage-v2-direct" }} + + + + + + {{- end }} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{- if eq .Values.artifactory.persistence.type "cluster-google-storage-v2" }} + + local + + + + 30 + 10000 + remote + + {{- end }} + + + {{- if .Values.artifactory.persistence.googleStorage.useInstanceCredentials }} + true + {{- else }} + false + {{- end }} + {{ .Values.artifactory.persistence.googleStorage.enableSignedUrlRedirect }} + google-cloud-storage + {{ .Values.artifactory.persistence.googleStorage.endpoint }} + {{ .Values.artifactory.persistence.googleStorage.httpsOnly }} + {{ .Values.artifactory.persistence.googleStorage.bucketName }} + {{ .Values.artifactory.persistence.googleStorage.path }} + {{ .Values.artifactory.persistence.googleStorage.bucketExists }} + + +{{- end }} +{{- if or (eq .Values.artifactory.persistence.type "aws-s3-v3") (eq .Values.artifactory.persistence.type "s3-storage-v3-direct") (eq .Values.artifactory.persistence.type "cluster-s3-storage-v3") (eq .Values.artifactory.persistence.type "s3-storage-v3-archive") }} + + + {{- if eq .Values.artifactory.persistence.type "aws-s3-v3" }} + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "s3-storage-v3-direct" }} + + + + + + {{- else if eq .Values.artifactory.persistence.type "cluster-s3-storage-v3" }} + + + + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "s3-storage-v3-archive" }} + + + + + + + {{- end }} + + {{- if or (eq .Values.artifactory.persistence.type "aws-s3-v3") (eq .Values.artifactory.persistence.type "s3-storage-v3-direct") (eq .Values.artifactory.persistence.type "cluster-s3-storage-v3") }} + + + {{ .Values.artifactory.persistence.maxCacheSize | int64}} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + {{- end }} + + {{- if eq .Values.artifactory.persistence.type "cluster-s3-storage-v3" }} + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + {{ .Values.artifactory.persistence.lenientLimit }} + + + + + remote + + + + local + + {{- end }} + + {{- with .Values.artifactory.persistence.awsS3V3 }} + + {{ .testConnection }} + {{- if .identity }} + {{ .identity }} + {{- end }} + {{- if .credential }} + {{ .credential }} + {{- end }} + {{ .region }} + {{ .bucketName }} + {{ .path }} + {{ .endpoint }} + {{- with .port }} + {{ . }} + {{- end }} + {{- with .useHttp }} + {{ . }} + {{- end }} + {{- with .maxConnections }} + {{ . }} + {{- end }} + {{- with .connectionTimeout }} + {{ . }} + {{- end }} + {{- with .socketTimeout }} + {{ . }} + {{- end }} + {{- with .kmsServerSideEncryptionKeyId }} + {{ . }} + {{- end }} + {{- with .kmsKeyRegion }} + {{ . }} + {{- end }} + {{- with .kmsCryptoMode }} + {{ . }} + {{- end }} + {{- if .useInstanceCredentials }} + true + {{- else }} + false + {{- end }} + {{ .usePresigning }} + {{ .signatureExpirySeconds }} + {{ .signedUrlExpirySeconds }} + {{- with .cloudFrontDomainName }} + {{ . }} + {{- end }} + {{- with .cloudFrontKeyPairId }} + {{ . }} + {{- end }} + {{- with .cloudFrontPrivateKey }} + {{ . }} + {{- end }} + {{- with .enableSignedUrlRedirect }} + {{ . }} + {{- end }} + {{- with .enablePathStyleAccess }} + {{ . }} + {{- end }} + {{- with .multiPartLimit }} + {{ . | int64 }} + {{- end }} + {{- with .multipartElementSize }} + {{ . | int64 }} + {{- end }} + + {{- end }} + +{{- end }} + +{{- if or (eq .Values.artifactory.persistence.type "azure-blob") (eq .Values.artifactory.persistence.type "azure-blob-storage-direct") (eq .Values.artifactory.persistence.type "cluster-azure-blob-storage") }} + + + {{- if or (eq .Values.artifactory.persistence.type "azure-blob") }} + + + + + + + + + + {{- else if eq .Values.artifactory.persistence.type "azure-blob-storage-direct" }} + + + + + + {{- else if eq .Values.artifactory.persistence.type "cluster-azure-blob-storage" }} + + + + + + + + + + + + + {{- end }} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{- if eq .Values.artifactory.persistence.type "cluster-azure-blob-storage" }} + + + crossNetworkStrategy + crossNetworkStrategy + {{ .Values.artifactory.persistence.redundancy }} + {{ .Values.artifactory.persistence.lenientLimit }} + + + + remote + + + local + + {{- end }} + + + {{ .Values.artifactory.persistence.azureBlob.accountName }} + {{ .Values.artifactory.persistence.azureBlob.accountKey }} + {{ .Values.artifactory.persistence.azureBlob.endpoint }} + {{ .Values.artifactory.persistence.azureBlob.containerName }} + {{ .Values.artifactory.persistence.azureBlob.multiPartLimit | int64 }} + {{ .Values.artifactory.persistence.azureBlob.multipartElementSize | int64 }} + {{ .Values.artifactory.persistence.azureBlob.testConnection }} + + +{{- end }} +{{- if eq .Values.artifactory.persistence.type "azure-blob-storage-v2-direct" -}} + + + + {{ .Values.artifactory.persistence.maxCacheSize | int64 }} + {{ .Values.artifactory.persistence.cacheProviderDir }} + {{- if .Values.artifactory.persistence.maxFileSizeLimit }} + {{.Values.artifactory.persistence.maxFileSizeLimit | int64}} + {{- end }} + {{- if .Values.artifactory.persistence.skipDuringUpload }} + {{.Values.artifactory.persistence.skipDuringUpload}} + {{- end }} + + + {{ .Values.artifactory.persistence.azureBlob.accountName }} + {{ .Values.artifactory.persistence.azureBlob.accountKey }} + {{ .Values.artifactory.persistence.azureBlob.endpoint }} + {{ .Values.artifactory.persistence.azureBlob.containerName }} + {{ .Values.artifactory.persistence.azureBlob.multiPartLimit | int64 }} + {{ .Values.artifactory.persistence.azureBlob.multipartElementSize | int64 }} + {{ .Values.artifactory.persistence.azureBlob.testConnection }} + + +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/installer-info.json b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/installer-info.json new file mode 100644 index 0000000000..79f42ed16d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/installer-info.json @@ -0,0 +1,32 @@ +{ + "productId": "Helm_artifactory/{{ .Chart.Version }}", + "features": [ + { + "featureId": "Platform/{{ printf "%s-%s" "kubernetes" .Capabilities.KubeVersion.Version }}" + }, + { + "featureId": "Database/{{ .Values.database.type }}" + }, + { + "featureId": "PostgreSQL_Enabled/{{ .Values.postgresql.enabled }}" + }, + { + "featureId": "Nginx_Enabled/{{ .Values.nginx.enabled }}" + }, + { + "featureId": "ArtifactoryPersistence_Type/{{ .Values.artifactory.persistence.type }}" + }, + { + "featureId": "SplitServicesToContainers_Enabled/{{ .Values.splitServicesToContainers }}" + }, + { + "featureId": "UnifiedSecretInstallation_Enabled/{{ .Values.artifactory.unifiedSecretInstallation }}" + }, + { + "featureId": "Filebeat_Enabled/{{ .Values.filebeat.enabled }}" + }, + { + "featureId": "ReplicaCount/{{ .Values.artifactory.replicaCount }}" + } + ] +} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrate.sh b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrate.sh new file mode 100644 index 0000000000..ba44160f47 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/migrate.sh @@ -0,0 +1,4311 @@ +#!/bin/bash + +# Flags +FLAG_Y="y" +FLAG_N="n" +FLAGS_Y_N="$FLAG_Y $FLAG_N" +FLAG_NOT_APPLICABLE="_NA_" + +CURRENT_VERSION=$1 + +WRAPPER_SCRIPT_TYPE_RPMDEB="RPMDEB" +WRAPPER_SCRIPT_TYPE_DOCKER_COMPOSE="DOCKERCOMPOSE" + +SENSITIVE_KEY_VALUE="__sensitive_key_hidden___" + +# Shared system keys +SYS_KEY_SHARED_JFROGURL="shared.jfrogUrl" +SYS_KEY_SHARED_SECURITY_JOINKEY="shared.security.joinKey" +SYS_KEY_SHARED_SECURITY_MASTERKEY="shared.security.masterKey" + +SYS_KEY_SHARED_NODE_ID="shared.node.id" +SYS_KEY_SHARED_JAVAHOME="shared.javaHome" + +SYS_KEY_SHARED_DATABASE_TYPE="shared.database.type" +SYS_KEY_SHARED_DATABASE_TYPE_VALUE_POSTGRES="postgresql" +SYS_KEY_SHARED_DATABASE_DRIVER="shared.database.driver" +SYS_KEY_SHARED_DATABASE_URL="shared.database.url" +SYS_KEY_SHARED_DATABASE_USERNAME="shared.database.username" +SYS_KEY_SHARED_DATABASE_PASSWORD="shared.database.password" + +SYS_KEY_SHARED_ELASTICSEARCH_URL="shared.elasticsearch.url" +SYS_KEY_SHARED_ELASTICSEARCH_USERNAME="shared.elasticsearch.username" +SYS_KEY_SHARED_ELASTICSEARCH_PASSWORD="shared.elasticsearch.password" +SYS_KEY_SHARED_ELASTICSEARCH_CLUSTERSETUP="shared.elasticsearch.clusterSetup" +SYS_KEY_SHARED_ELASTICSEARCH_UNICASTFILE="shared.elasticsearch.unicastFile" +SYS_KEY_SHARED_ELASTICSEARCH_CLUSTERSETUP_VALUE="YES" + +# Define this in product specific script. Should contain the path to unitcast file +# File used by insight server to write cluster active nodes info. This will be read by elasticsearch +#SYS_KEY_SHARED_ELASTICSEARCH_UNICASTFILE_VALUE="" + +SYS_KEY_RABBITMQ_ACTIVE_NODE_NAME="shared.rabbitMq.active.node.name" +SYS_KEY_RABBITMQ_ACTIVE_NODE_IP="shared.rabbitMq.active.node.ip" + +# Filenames +FILE_NAME_SYSTEM_YAML="system.yaml" +FILE_NAME_JOIN_KEY="join.key" +FILE_NAME_MASTER_KEY="master.key" +FILE_NAME_INSTALLER_YAML="installer.yaml" + +# Global constants used in business logic +NODE_TYPE_STANDALONE="standalone" +NODE_TYPE_CLUSTER_NODE="node" +NODE_TYPE_DATABASE="database" + +# External(isable) databases +DATABASE_POSTGRES="POSTGRES" +DATABASE_ELASTICSEARCH="ELASTICSEARCH" +DATABASE_RABBITMQ="RABBITMQ" + +POSTGRES_LABEL="PostgreSQL" +ELASTICSEARCH_LABEL="Elasticsearch" +RABBITMQ_LABEL="Rabbitmq" + +ARTIFACTORY_LABEL="Artifactory" +JFMC_LABEL="Mission Control" +DISTRIBUTION_LABEL="Distribution" +XRAY_LABEL="Xray" + +POSTGRES_CONTAINER="postgres" +ELASTICSEARCH_CONTAINER="elasticsearch" +RABBITMQ_CONTAINER="rabbitmq" +REDIS_CONTAINER="redis" + +#Adding a small timeout before a read ensures it is positioned correctly in the screen +read_timeout=0.5 + +# Options related to data directory location +PROMPT_DATA_DIR_LOCATION="Installation Directory" +KEY_DATA_DIR_LOCATION="installer.data_dir" + +SYS_KEY_SHARED_NODE_HAENABLED="shared.node.haEnabled" +PROMPT_ADD_TO_CLUSTER="Are you adding an additional node to an existing product cluster?" +KEY_ADD_TO_CLUSTER="installer.ha" +VALID_VALUES_ADD_TO_CLUSTER="$FLAGS_Y_N" + +MESSAGE_POSTGRES_INSTALL="The installer can install a $POSTGRES_LABEL database, or you can connect to an existing compatible $POSTGRES_LABEL database\n(compatible databases: https://www.jfrog.com/confluence/display/JFROG/System+Requirements#SystemRequirements-RequirementsMatrix)" +PROMPT_POSTGRES_INSTALL="Do you want to install $POSTGRES_LABEL?" +KEY_POSTGRES_INSTALL="installer.install_postgresql" +VALID_VALUES_POSTGRES_INSTALL="$FLAGS_Y_N" + +# Postgres connection details +RPM_DEB_POSTGRES_HOME_DEFAULT="/var/opt/jfrog/postgres" +RPM_DEB_MESSAGE_STANDALONE_POSTGRES_DATA="$POSTGRES_LABEL home will have data and its configuration" +RPM_DEB_PROMPT_STANDALONE_POSTGRES_DATA="Type desired $POSTGRES_LABEL home location" +RPM_DEB_KEY_STANDALONE_POSTGRES_DATA="installer.postgresql.home" + +MESSAGE_DATABASE_URL="Provide the database connection details" +PROMPT_DATABASE_URL(){ + local databaseURlExample= + case "$PRODUCT_NAME" in + $ARTIFACTORY_LABEL) + databaseURlExample="jdbc:postgresql://:/artifactory" + ;; + $JFMC_LABEL) + databaseURlExample="postgresql://:/mission_control?sslmode=disable" + ;; + $DISTRIBUTION_LABEL) + databaseURlExample="jdbc:postgresql://:/distribution?sslmode=disable" + ;; + $XRAY_LABEL) + databaseURlExample="postgres://:/xraydb?sslmode=disable" + ;; + esac + if [ -z "$databaseURlExample" ]; then + echo -n "$POSTGRES_LABEL URL" # For consistency with username and password + return + fi + echo -n "$POSTGRES_LABEL url. Example: [$databaseURlExample]" +} +REGEX_DATABASE_URL(){ + local databaseURlExample= + case "$PRODUCT_NAME" in + $ARTIFACTORY_LABEL) + databaseURlExample="jdbc:postgresql://.*/artifactory.*" + ;; + $JFMC_LABEL) + databaseURlExample="postgresql://.*/mission_control.*" + ;; + $DISTRIBUTION_LABEL) + databaseURlExample="jdbc:postgresql://.*/distribution.*" + ;; + $XRAY_LABEL) + databaseURlExample="postgres://.*/xraydb.*" + ;; + esac + echo -n "^$databaseURlExample\$" +} +ERROR_MESSAGE_DATABASE_URL="Invalid $POSTGRES_LABEL URL" +KEY_DATABASE_URL="$SYS_KEY_SHARED_DATABASE_URL" +#NOTE: It is important to display the label. Since the message may be hidden if URL is known +PROMPT_DATABASE_USERNAME="$POSTGRES_LABEL username" +KEY_DATABASE_USERNAME="$SYS_KEY_SHARED_DATABASE_USERNAME" +#NOTE: It is important to display the label. Since the message may be hidden if URL is known +PROMPT_DATABASE_PASSWORD="$POSTGRES_LABEL password" +KEY_DATABASE_PASSWORD="$SYS_KEY_SHARED_DATABASE_PASSWORD" +IS_SENSITIVE_DATABASE_PASSWORD="$FLAG_Y" + +MESSAGE_STANDALONE_ELASTICSEARCH_INSTALL="The installer can install a $ELASTICSEARCH_LABEL database or you can connect to an existing compatible $ELASTICSEARCH_LABEL database" +PROMPT_STANDALONE_ELASTICSEARCH_INSTALL="Do you want to install $ELASTICSEARCH_LABEL?" +KEY_STANDALONE_ELASTICSEARCH_INSTALL="installer.install_elasticsearch" +VALID_VALUES_STANDALONE_ELASTICSEARCH_INSTALL="$FLAGS_Y_N" + +# Elasticsearch connection details +MESSAGE_ELASTICSEARCH_DETAILS="Provide the $ELASTICSEARCH_LABEL connection details" +PROMPT_ELASTICSEARCH_URL="$ELASTICSEARCH_LABEL URL" +KEY_ELASTICSEARCH_URL="$SYS_KEY_SHARED_ELASTICSEARCH_URL" + +PROMPT_ELASTICSEARCH_USERNAME="$ELASTICSEARCH_LABEL username" +KEY_ELASTICSEARCH_USERNAME="$SYS_KEY_SHARED_ELASTICSEARCH_USERNAME" + +PROMPT_ELASTICSEARCH_PASSWORD="$ELASTICSEARCH_LABEL password" +KEY_ELASTICSEARCH_PASSWORD="$SYS_KEY_SHARED_ELASTICSEARCH_PASSWORD" +IS_SENSITIVE_ELASTICSEARCH_PASSWORD="$FLAG_Y" + +# Cluster related questions +MESSAGE_CLUSTER_MASTER_KEY="Provide the cluster's master key. It can be found in the data directory of the first node under /etc/security/master.key" +PROMPT_CLUSTER_MASTER_KEY="Master Key" +KEY_CLUSTER_MASTER_KEY="$SYS_KEY_SHARED_SECURITY_MASTERKEY" +IS_SENSITIVE_CLUSTER_MASTER_KEY="$FLAG_Y" + +MESSAGE_JOIN_KEY="The Join key is the secret key used to establish trust between services in the JFrog Platform.\n(You can copy the Join Key from Admin > User Management > Settings)" +PROMPT_JOIN_KEY="Join Key" +KEY_JOIN_KEY="$SYS_KEY_SHARED_SECURITY_JOINKEY" +IS_SENSITIVE_JOIN_KEY="$FLAG_Y" +REGEX_JOIN_KEY="^[a-zA-Z0-9]{16,}\$" +ERROR_MESSAGE_JOIN_KEY="Invalid Join Key" + +# Rabbitmq related cluster information +MESSAGE_RABBITMQ_ACTIVE_NODE_NAME="Provide an active ${RABBITMQ_LABEL} node name. Run the command [ hostname -s ] on any of the existing nodes in the product cluster to get this" +PROMPT_RABBITMQ_ACTIVE_NODE_NAME="${RABBITMQ_LABEL} active node name" +KEY_RABBITMQ_ACTIVE_NODE_NAME="$SYS_KEY_RABBITMQ_ACTIVE_NODE_NAME" + +# Rabbitmq related cluster information (necessary only for docker-compose) +PROMPT_RABBITMQ_ACTIVE_NODE_IP="${RABBITMQ_LABEL} active node ip" +KEY_RABBITMQ_ACTIVE_NODE_IP="$SYS_KEY_RABBITMQ_ACTIVE_NODE_IP" + +MESSAGE_JFROGURL(){ + echo -e "The JFrog URL allows ${PRODUCT_NAME} to connect to a JFrog Platform Instance.\n(You can copy the JFrog URL from Administration > User Management > Settings > Connection details)" +} +PROMPT_JFROGURL="JFrog URL" +KEY_JFROGURL="$SYS_KEY_SHARED_JFROGURL" +REGEX_JFROGURL="^https?://.*:{0,}[0-9]{0,4}\$" +ERROR_MESSAGE_JFROGURL="Invalid JFrog URL" + + +# Set this to FLAG_Y on upgrade +IS_UPGRADE="${FLAG_N}" + +# This belongs in JFMC but is the ONLY one that needs it so keeping it here for now. Can be made into a method and overridden if necessary +MESSAGE_MULTIPLE_PG_SCHEME="Please setup $POSTGRES_LABEL with schema as described in https://www.jfrog.com/confluence/display/JFROG/Installing+Mission+Control" + +_getMethodOutputOrVariableValue() { + unset EFFECTIVE_MESSAGE + local keyToSearch=$1 + local effectiveMessage= + local result="0" + # logSilly "Searching for method: [$keyToSearch]" + LC_ALL=C type "$keyToSearch" > /dev/null 2>&1 || result="$?" + if [[ "$result" == "0" ]]; then + # logSilly "Found method for [$keyToSearch]" + EFFECTIVE_MESSAGE="$($keyToSearch)" + return + fi + eval EFFECTIVE_MESSAGE=\${$keyToSearch} + if [ ! -z "$EFFECTIVE_MESSAGE" ]; then + return + fi + # logSilly "Didn't find method or variable for [$keyToSearch]" +} + + +# REF https://misc.flogisoft.com/bash/tip_colors_and_formatting +cClear="\e[0m" +cBlue="\e[38;5;69m" +cRedDull="\e[1;31m" +cYellow="\e[1;33m" +cRedBright="\e[38;5;197m" +cBold="\e[1m" + + +_loggerGetModeRaw() { + local MODE="$1" + case $MODE in + INFO) + printf "" + ;; + DEBUG) + printf "%s" "[${MODE}] " + ;; + WARN) + printf "${cRedDull}%s%s${cClear}" "[" "${MODE}" "] " + ;; + ERROR) + printf "${cRedBright}%s%s${cClear}" "[" "${MODE}" "] " + ;; + esac +} + + +_loggerGetMode() { + local MODE="$1" + case $MODE in + INFO) + printf "${cBlue}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + DEBUG) + printf "%-7s" "[${MODE}]" + ;; + WARN) + printf "${cRedDull}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + ERROR) + printf "${cRedBright}%s%-5s%s${cClear}" "[" "${MODE}" "]" + ;; + esac +} + +# Capitalises the first letter of the message +_loggerGetMessage() { + local originalMessage="$*" + local firstChar=$(echo "${originalMessage:0:1}" | awk '{ print toupper($0) }') + local resetOfMessage="${originalMessage:1}" + echo "$firstChar$resetOfMessage" +} + +# The spec also says content should be left-trimmed but this is not necessary in our case. We don't reach the limit. +_loggerGetStackTrace() { + printf "%s%-30s%s" "[" "$1:$2" "]" +} + +_loggerGetThread() { + printf "%s" "[main]" +} + +_loggerGetServiceType() { + printf "%s%-5s%s" "[" "shell" "]" +} + +#Trace ID is not applicable to scripts +_loggerGetTraceID() { + printf "%s" "[]" +} + +logRaw() { + echo "" + printf "$1" + echo "" +} + +logBold(){ + echo "" + printf "${cBold}$1${cClear}" + echo "" +} + +# The date binary works differently based on whether it is GNU/BSD +is_date_supported=0 +date --version > /dev/null 2>&1 || is_date_supported=1 +IS_GNU=$(echo $is_date_supported) + +_loggerGetTimestamp() { + if [ "${IS_GNU}" == "0" ]; then + echo -n $(date -u +%FT%T.%3NZ) + else + echo -n $(date -u +%FT%T.000Z) + fi +} + +# https://www.shellscript.sh/tips/spinner/ +_spin() +{ + spinner="/|\\-/|\\-" + while : + do + for i in `seq 0 7` + do + echo -n "${spinner:$i:1}" + echo -en "\010" + sleep 1 + done + done +} + +showSpinner() { + # Start the Spinner: + _spin & + # Make a note of its Process ID (PID): + SPIN_PID=$! + # Kill the spinner on any signal, including our own exit. + trap "kill -9 $SPIN_PID" `seq 0 15` &> /dev/null || return 0 +} + +stopSpinner() { + local occurrences=$(ps -ef | grep -wc "${SPIN_PID}") + let "occurrences+=0" + # validate that it is present (2 since this search itself will show up in the results) + if [ $occurrences -gt 1 ]; then + kill -9 $SPIN_PID &>/dev/null || return 0 + wait $SPIN_ID &>/dev/null + fi +} + +_getEffectiveMessage(){ + local MESSAGE="$1" + local MODE=${2-"INFO"} + + if [ -z "$CONTEXT" ]; then + CONTEXT=$(caller) + fi + + _EFFECTIVE_MESSAGE= + if [ -z "$LOG_BEHAVIOR_ADD_META" ]; then + _EFFECTIVE_MESSAGE="$(_loggerGetModeRaw $MODE)$(_loggerGetMessage $MESSAGE)" + else + local SERVICE_TYPE="script" + local TRACE_ID="" + local THREAD="main" + + local CONTEXT_LINE=$(echo "$CONTEXT" | awk '{print $1}') + local CONTEXT_FILE=$(echo "$CONTEXT" | awk -F"/" '{print $NF}') + + _EFFECTIVE_MESSAGE="$(_loggerGetTimestamp) $(_loggerGetServiceType) $(_loggerGetMode $MODE) $(_loggerGetTraceID) $(_loggerGetStackTrace $CONTEXT_FILE $CONTEXT_LINE) $(_loggerGetThread) - $(_loggerGetMessage $MESSAGE)" + fi + CONTEXT= +} + +# Important - don't call any log method from this method. Will become an infinite loop. Use echo to debug +_logToFile() { + local MODE=${1-"INFO"} + local targetFile="$LOG_BEHAVIOR_ADD_REDIRECTION" + # IF the file isn't passed, abort + if [ -z "$targetFile" ]; then + return + fi + # IF this is not being run in verbose mode and mode is debug or lower, abort + if [ "${VERBOSE_MODE}" != "$FLAG_Y" ] && [ "${VERBOSE_MODE}" != "true" ] && [ "${VERBOSE_MODE}" != "debug" ]; then + if [ "$MODE" == "DEBUG" ] || [ "$MODE" == "SILLY" ]; then + return + fi + fi + + # Create the file if it doesn't exist + if [ ! -f "${targetFile}" ]; then + return + # touch $targetFile > /dev/null 2>&1 || true + fi + # # Make it readable + # chmod 640 $targetFile > /dev/null 2>&1 || true + + # Log contents + printf "%s\n" "$_EFFECTIVE_MESSAGE" >> "$targetFile" || true +} + +logger() { + if [ "$LOG_BEHAVIOR_ADD_NEW_LINE" == "$FLAG_Y" ]; then + echo "" + fi + _getEffectiveMessage "$@" + local MODE=${2-"INFO"} + printf "%s\n" "$_EFFECTIVE_MESSAGE" + _logToFile "$MODE" +} + +logDebug(){ + VERBOSE_MODE=${VERBOSE_MODE-"false"} + CONTEXT=$(caller) + if [ "${VERBOSE_MODE}" == "$FLAG_Y" ] || [ "${VERBOSE_MODE}" == "true" ] || [ "${VERBOSE_MODE}" == "debug" ];then + logger "$1" "DEBUG" + else + logger "$1" "DEBUG" >&6 + fi + CONTEXT= +} + +logSilly(){ + VERBOSE_MODE=${VERBOSE_MODE-"false"} + CONTEXT=$(caller) + if [ "${VERBOSE_MODE}" == "silly" ];then + logger "$1" "DEBUG" + else + logger "$1" "DEBUG" >&6 + fi + CONTEXT= +} + +logError() { + CONTEXT=$(caller) + logger "$1" "ERROR" + CONTEXT= +} + +errorExit () { + CONTEXT=$(caller) + logger "$1" "ERROR" + CONTEXT= + exit 1 +} + +warn () { + CONTEXT=$(caller) + logger "$1" "WARN" + CONTEXT= +} + +note () { + CONTEXT=$(caller) + logger "$1" "NOTE" + CONTEXT= +} + +bannerStart() { + title=$1 + echo + echo -e "\033[1m${title}\033[0m" + echo +} + +bannerSection() { + title=$1 + echo + echo -e "******************************** ${title} ********************************" + echo +} + +bannerSubSection() { + title=$1 + echo + echo -e "************** ${title} *******************" + echo +} + +bannerMessge() { + title=$1 + echo + echo -e "********************************" + echo -e "${title}" + echo -e "********************************" + echo +} + +setRed () { + local input="$1" + echo -e \\033[31m${input}\\033[0m +} +setGreen () { + local input="$1" + echo -e \\033[32m${input}\\033[0m +} +setYellow () { + local input="$1" + echo -e \\033[33m${input}\\033[0m +} + +logger_addLinebreak () { + echo -e "---\n" +} + +bannerImportant() { + title=$1 + local bold="\033[1m" + local noColour="\033[0m" + echo + echo -e "${bold}######################################## IMPORTANT ########################################${noColour}" + echo -e "${bold}${title}${noColour}" + echo -e "${bold}###########################################################################################${noColour}" + echo +} + +bannerEnd() { + #TODO pass a title and calculate length dynamically so that start and end look alike + echo + echo "*****************************************************************************" + echo +} + +banner() { + title=$1 + content=$2 + bannerStart "${title}" + echo -e "$content" +} + +# The logic below helps us redirect content we'd normally hide to the log file. + # + # We have several commands which clutter the console with output and so use + # `cmd > /dev/null` - this redirects the command's output to null. + # + # However, the information we just hid maybe useful for support. Using the code pattern + # `cmd >&6` (instead of `cmd> >/dev/null` ), the command's output is hidden from the console + # but redirected to the installation log file + # + +#Default value of 6 is just null +exec 6>>/dev/null +redirectLogsToFile() { + echo "" + # local file=$1 + + # [ ! -z "${file}" ] || return 0 + + # local logDir=$(dirname "$file") + + # if [ ! -f "${file}" ]; then + # [ -d "${logDir}" ] || mkdir -p ${logDir} || \ + # ( echo "WARNING : Could not create parent directory (${logDir}) to redirect console log : ${file}" ; return 0 ) + # fi + + # #6 now points to the log file + # exec 6>>${file} + # #reference https://unix.stackexchange.com/questions/145651/using-exec-and-tee-to-redirect-logs-to-stdout-and-a-log-file-in-the-same-time + # exec 2>&1 > >(tee -a "${file}") +} + +# Check if a give key contains any sensitive string as part of it +# Based on the result, the caller can decide its value can be displayed or not +# Sample usage : isKeySensitive "${key}" && displayValue="******" || displayValue=${value} +isKeySensitive(){ + local key=$1 + local sensitiveKeys="password|secret|key|token" + + if [ -z "${key}" ]; then + return 1 + else + local lowercaseKey=$(echo "${key}" | tr '[:upper:]' '[:lower:]' 2>/dev/null) + [[ "${lowercaseKey}" =~ ${sensitiveKeys} ]] && return 0 || return 1 + fi +} + +getPrintableValueOfKey(){ + local displayValue= + local key="$1" + if [ -z "$key" ]; then + # This is actually an incorrect usage of this method but any logging will cause unexpected content in the caller + echo -n "" + return + fi + + local value="$2" + isKeySensitive "${key}" && displayValue="$SENSITIVE_KEY_VALUE" || displayValue="${value}" + echo -n $displayValue +} + +_createConsoleLog(){ + if [ -z "${JF_PRODUCT_HOME}" ]; then + return + fi + local targetFile="${JF_PRODUCT_HOME}/var/log/console.log" + mkdir -p "${JF_PRODUCT_HOME}/var/log" || true + if [ ! -f ${targetFile} ]; then + touch $targetFile > /dev/null 2>&1 || true + fi + chmod 640 $targetFile > /dev/null 2>&1 || true +} + +# Output from application's logs are piped to this method. It checks a configuration variable to determine if content should be logged to +# the common console.log file +redirectServiceLogsToFile() { + + local result="0" + # check if the function getSystemValue exists + LC_ALL=C type getSystemValue > /dev/null 2>&1 || result="$?" + if [[ "$result" != "0" ]]; then + warn "Couldn't find the systemYamlHelper. Skipping log redirection" + return 0 + fi + + getSystemValue "shared.consoleLog" "NOT_SET" + if [[ "${YAML_VALUE}" == "false" ]]; then + logger "Redirection is set to false. Skipping log redirection" + return 0; + fi + + if [ -z "${JF_PRODUCT_HOME}" ] || [ "${JF_PRODUCT_HOME}" == "" ]; then + warn "JF_PRODUCT_HOME is unavailable. Skipping log redirection" + return 0 + fi + + local targetFile="${JF_PRODUCT_HOME}/var/log/console.log" + + _createConsoleLog + + while read -r line; do + printf '%s\n' "${line}" >> $targetFile || return 0 # Don't want to log anything - might clutter the screen + done +} + +## Display environment variables starting with JF_ along with its value +## Value of sensitive keys will be displayed as "******" +## +## Sample Display : +## +## ======================== +## JF Environment variables +## ======================== +## +## JF_SHARED_NODE_ID : locahost +## JF_SHARED_JOINKEY : ****** +## +## +displayEnv() { + local JFEnv=$(printenv | grep ^JF_ 2>/dev/null) + local key= + local value= + + if [ -z "${JFEnv}" ]; then + return + fi + + cat << ENV_START_MESSAGE + +======================== +JF Environment variables +======================== +ENV_START_MESSAGE + + for entry in ${JFEnv}; do + key=$(echo "${entry}" | awk -F'=' '{print $1}') + value=$(echo "${entry}" | awk -F'=' '{print $2}') + + isKeySensitive "${key}" && value="******" || value=${value} + + printf "\n%-35s%s" "${key}" " : ${value}" + done + echo; +} + +_addLogRotateConfiguration() { + logDebug "Method ${FUNCNAME[0]}" + # mandatory inputs + local confFile="$1" + local logFile="$2" + + # Method available in _ioOperations.sh + LC_ALL=C type io_setYQPath > /dev/null 2>&1 || return 1 + + io_setYQPath + + # Method available in _systemYamlHelper.sh + LC_ALL=C type getSystemValue > /dev/null 2>&1 || return 1 + + local frequency="daily" + local archiveFolder="archived" + + local compressLogFiles= + getSystemValue "shared.logging.rotation.compress" "true" + if [[ "${YAML_VALUE}" == "true" ]]; then + compressLogFiles="compress" + fi + + getSystemValue "shared.logging.rotation.maxFiles" "10" + local noOfBackupFiles="${YAML_VALUE}" + + getSystemValue "shared.logging.rotation.maxSizeMb" "25" + local sizeOfFile="${YAML_VALUE}M" + + logDebug "Adding logrotate configuration for [$logFile] to [$confFile]" + + # Add configuration to file + local confContent=$(cat << LOGROTATECONF +$logFile { + $frequency + missingok + rotate $noOfBackupFiles + $compressLogFiles + notifempty + olddir $archiveFolder + dateext + extension .log + dateformat -%Y-%m-%d + size ${sizeOfFile} +} +LOGROTATECONF +) + echo "${confContent}" > ${confFile} || return 1 +} + +_operationIsBySameUser() { + local targetUser="$1" + local currentUserID=$(id -u) + local currentUserName=$(id -un) + + if [ $currentUserID == $targetUser ] || [ $currentUserName == $targetUser ]; then + echo -n "yes" + else + echo -n "no" + fi +} + +_addCronJobForLogrotate() { + logDebug "Method ${FUNCNAME[0]}" + + # Abort if logrotate is not available + [ "$(io_commandExists 'crontab')" != "yes" ] && warn "cron is not available" && return 1 + + # mandatory inputs + local productHome="$1" + local confFile="$2" + local cronJobOwner="$3" + + # We want to use our binary if possible. It may be more recent than the one in the OS + local logrotateBinary="$productHome/app/third-party/logrotate/logrotate" + + if [ ! -f "$logrotateBinary" ]; then + logrotateBinary="logrotate" + [ "$(io_commandExists 'logrotate')" != "yes" ] && warn "logrotate is not available" && return 1 + fi + local cmd="$logrotateBinary ${confFile} --state $productHome/var/etc/logrotate/logrotate-state" #--verbose + + id -u $cronJobOwner > /dev/null 2>&1 || { warn "User $cronJobOwner does not exist. Aborting logrotate configuration" && return 1; } + + # Remove the existing line + removeLogRotation "$productHome" "$cronJobOwner" || true + + # Run logrotate daily at 23:55 hours + local cronInterval="55 23 * * * $cmd" + + local standaloneMode=$(_operationIsBySameUser "$cronJobOwner") + + # If this is standalone mode, we cannot use -u - the user running this process may not have the necessary privileges + if [ "$standaloneMode" == "no" ]; then + (crontab -l -u $cronJobOwner 2>/dev/null; echo "$cronInterval") | crontab -u $cronJobOwner - + else + (crontab -l 2>/dev/null; echo "$cronInterval") | crontab - + fi +} + +## Configure logrotate for a product +## Failure conditions: +## If logrotation could not be setup for some reason +## Parameters: +## $1: The product name +## $2: The product home +## Depends on global: none +## Updates global: none +## Returns: NA + +configureLogRotation() { + logDebug "Method ${FUNCNAME[0]}" + + # mandatory inputs + local productName="$1" + if [ -z $productName ]; then + warn "Incorrect usage. A product name is necessary for configuring log rotation" && return 1 + fi + + local productHome="$2" + if [ -z $productHome ]; then + warn "Incorrect usage. A product home folder is necessary for configuring log rotation" && return 1 + fi + + local logFile="${productHome}/var/log/console.log" + if [[ $(uname) == "Darwin" ]]; then + logger "Log rotation for [$logFile] has not been configured. Please setup manually" + return 0 + fi + + local userID="$3" + if [ -z $userID ]; then + warn "Incorrect usage. A userID is necessary for configuring log rotation" && return 1 + fi + + local groupID=${4:-$userID} + local logConfigOwner=${5:-$userID} + + logDebug "Configuring log rotation as user [$userID], group [$groupID], effective cron User [$logConfigOwner]" + + local errorMessage="Could not configure logrotate. Please configure log rotation of the file: [$logFile] manually" + + local confFile="${productHome}/var/etc/logrotate/logrotate.conf" + + # TODO move to recursive method + createDir "${productHome}" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/log" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/log/archived" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + + # TODO move to recursive method + createDir "${productHome}/var/etc" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + createDir "${productHome}/var/etc/logrotate" "$logConfigOwner" || { warn "${errorMessage}" && return 1; } + + # conf file should be owned by the user running the script + createFile "${confFile}" "${logConfigOwner}" || { warn "Could not create configuration file [$confFile]" return 1; } + + _addLogRotateConfiguration "${confFile}" "${logFile}" "$userID" "$groupID" || { warn "${errorMessage}" && return 1; } + _addCronJobForLogrotate "${productHome}" "${confFile}" "${logConfigOwner}" || { warn "${errorMessage}" && return 1; } +} + +_pauseExecution() { + if [ "${VERBOSE_MODE}" == "debug" ]; then + + local breakPoint="$1" + if [ ! -z "$breakPoint" ]; then + printf "${cBlue}Breakpoint${cClear} [$breakPoint] " + echo "" + fi + printf "${cBlue}Press enter once you are ready to continue${cClear}" + read -s choice + echo "" + fi +} + +# removeLogRotation "$productHome" "$cronJobOwner" || true +removeLogRotation() { + logDebug "Method ${FUNCNAME[0]}" + if [[ $(uname) == "Darwin" ]]; then + logDebug "Not implemented for Darwin." + return 0 + fi + local productHome="$1" + local cronJobOwner="$2" + local standaloneMode=$(_operationIsBySameUser "$cronJobOwner") + + local confFile="${productHome}/var/etc/logrotate/logrotate.conf" + + if [ "$standaloneMode" == "no" ]; then + crontab -l -u $cronJobOwner 2>/dev/null | grep -v "$confFile" | crontab -u $cronJobOwner - + else + crontab -l 2>/dev/null | grep -v "$confFile" | crontab - + fi +} + +# NOTE: This method does not check the configuration to see if redirection is necessary. +# This is intentional. If we don't redirect, tomcat logs might get redirected to a folder/file +# that does not exist, causing the service itself to not start +setupTomcatRedirection() { + logDebug "Method ${FUNCNAME[0]}" + local consoleLog="${JF_PRODUCT_HOME}/var/log/console.log" + _createConsoleLog + export CATALINA_OUT="${consoleLog}" +} + +setupScriptLogsRedirection() { + logDebug "Method ${FUNCNAME[0]}" + if [ -z "${JF_PRODUCT_HOME}" ]; then + logDebug "No JF_PRODUCT_HOME. Returning" + return + fi + # Create the console.log file if it is not already present + # _createConsoleLog || true + # # Ensure any logs (logger/logError/warn) also get redirected to the console.log + # # Using installer.log as a temparory fix. Please change this to console.log once INST-291 is fixed + export LOG_BEHAVIOR_ADD_REDIRECTION="${JF_PRODUCT_HOME}/var/log/console.log" + export LOG_BEHAVIOR_ADD_META="$FLAG_Y" +} + +# Returns Y if this method is run inside a container +isRunningInsideAContainer() { + local check1=$(grep -sq 'docker\|kubepods' /proc/1/cgroup; echo $?) + local check2=$(grep -sq 'containers' /proc/self/mountinfo; echo $?) + if [[ $check1 == 0 || $check2 == 0 || -f "/.dockerenv" ]]; then + echo -n "$FLAG_Y" + else + echo -n "$FLAG_N" + fi +} + +POSTGRES_USER=999 +NGINX_USER=104 +NGINX_GROUP=107 +ES_USER=1000 +REDIS_USER=999 +MONGO_USER=999 +RABBITMQ_USER=999 +LOG_FILE_PERMISSION=640 +PID_FILE_PERMISSION=644 + +# Copy file +copyFile(){ + local source=$1 + local target=$2 + local mode=${3:-overwrite} + local enableVerbose=${4:-"${FLAG_N}"} + local verboseFlag="" + + if [ ! -z "${enableVerbose}" ] && [ "${enableVerbose}" == "${FLAG_Y}" ]; then + verboseFlag="-v" + fi + + if [[ ! ( $source && $target ) ]]; then + warn "Source and target is mandatory to copy file" + return 1 + fi + + if [[ -f "${target}" ]]; then + [[ "$mode" = "overwrite" ]] && ( cp ${verboseFlag} -f "$source" "$target" || errorExit "Unable to copy file, command : cp -f ${source} ${target}") || true + else + cp ${verboseFlag} -f "$source" "$target" || errorExit "Unable to copy file, command : cp -f ${source} ${target}" + fi +} + +# Copy files recursively from given source directory to destination directory +# This method wil copy but will NOT overwrite +# Destination will be created if its not available +copyFilesNoOverwrite(){ + local src=$1 + local dest=$2 + local enableVerboseCopy="${3:-${FLAG_Y}}" + + if [[ -z "${src}" || -z "${dest}" ]]; then + return + fi + + if [ -d "${src}" ] && [ "$(ls -A ${src})" ]; then + local relativeFilePath="" + local targetFilePath="" + + for file in $(find ${src} -type f 2>/dev/null) ; do + # Derive relative path and attach it to destination + # Example : + # src=/extra_config + # dest=/var/opt/jfrog/artifactory/etc + # file=/extra_config/config.xml + # relativeFilePath=config.xml + # targetFilePath=/var/opt/jfrog/artifactory/etc/config.xml + relativeFilePath=${file/${src}/} + targetFilePath=${dest}${relativeFilePath} + + createDir "$(dirname "$targetFilePath")" + copyFile "${file}" "${targetFilePath}" "no_overwrite" "${enableVerboseCopy}" + done + fi +} + +# TODO : WINDOWS ? +# Check the max open files and open processes set on the system +checkULimits () { + local minMaxOpenFiles=${1:-32000} + local minMaxOpenProcesses=${2:-1024} + local setValue=${3:-true} + local warningMsgForFiles=${4} + local warningMsgForProcesses=${5} + + logger "Checking open files and processes limits" + + local currentMaxOpenFiles=$(ulimit -n) + logger "Current max open files is $currentMaxOpenFiles" + if [ ${currentMaxOpenFiles} != "unlimited" ] && [ "$currentMaxOpenFiles" -lt "$minMaxOpenFiles" ]; then + if [ "${setValue}" ]; then + ulimit -n "${minMaxOpenFiles}" >/dev/null 2>&1 || warn "Max number of open files $currentMaxOpenFiles is low!" + [ -z "${warningMsgForFiles}" ] || warn "${warningMsgForFiles}" + else + errorExit "Max number of open files $currentMaxOpenFiles, is too low. Cannot run the application!" + fi + fi + + local currentMaxOpenProcesses=$(ulimit -u) + logger "Current max open processes is $currentMaxOpenProcesses" + if [ "$currentMaxOpenProcesses" != "unlimited" ] && [ "$currentMaxOpenProcesses" -lt "$minMaxOpenProcesses" ]; then + if [ "${setValue}" ]; then + ulimit -u "${minMaxOpenProcesses}" >/dev/null 2>&1 || warn "Max number of open files $currentMaxOpenFiles is low!" + [ -z "${warningMsgForProcesses}" ] || warn "${warningMsgForProcesses}" + else + errorExit "Max number of open files $currentMaxOpenProcesses, is too low. Cannot run the application!" + fi + fi +} + +createDirs() { + local appDataDir=$1 + local serviceName=$2 + local folders="backup bootstrap data etc logs work" + + [ -z "${appDataDir}" ] && errorExit "An application directory is mandatory to create its data structure" || true + [ -z "${serviceName}" ] && errorExit "A service name is mandatory to create service data structure" || true + + for folder in ${folders} + do + folder=${appDataDir}/${folder}/${serviceName} + if [ ! -d "${folder}" ]; then + logger "Creating folder : ${folder}" + mkdir -p "${folder}" || errorExit "Failed to create ${folder}" + fi + done +} + + +testReadWritePermissions () { + local dir_to_check=$1 + local error=false + + [ -d ${dir_to_check} ] || errorExit "'${dir_to_check}' is not a directory" + + local test_file=${dir_to_check}/test-permissions + + # Write file + if echo test > ${test_file} 1> /dev/null 2>&1; then + # Write succeeded. Testing read... + if cat ${test_file} > /dev/null; then + rm -f ${test_file} + else + error=true + fi + else + error=true + fi + + if [ ${error} == true ]; then + return 1 + else + return 0 + fi +} + +# Test directory has read/write permissions for current user +testDirectoryPermissions () { + local dir_to_check=$1 + local error=false + + [ -d ${dir_to_check} ] || errorExit "'${dir_to_check}' is not a directory" + + local u_id=$(id -u) + local id_str="id ${u_id}" + + logger "Testing directory ${dir_to_check} has read/write permissions for user ${id_str}" + + if ! testReadWritePermissions ${dir_to_check}; then + error=true + fi + + if [ "${error}" == true ]; then + local stat_data=$(stat -Lc "Directory: %n, permissions: %a, owner: %U, group: %G" ${dir_to_check}) + logger "###########################################################" + logger "${dir_to_check} DOES NOT have proper permissions for user ${id_str}" + logger "${stat_data}" + logger "Mounted directory must have read/write permissions for user ${id_str}" + logger "###########################################################" + errorExit "Directory ${dir_to_check} has bad permissions for user ${id_str}" + fi + logger "Permissions for ${dir_to_check} are good" +} + +# Utility method to create a directory path recursively with chown feature as +# Failure conditions: +## Exits if unable to create a directory +# Parameters: +## $1: Root directory from where the path can be created +## $2: List of recursive child directories separated by space +## $3: user who should own the directory. Optional +## $4: group who should own the directory. Optional +# Depends on global: none +# Updates global: none +# Returns: NA +# +# Usage: +# createRecursiveDir "/opt/jfrog/product/var" "bootstrap tomcat lib" "user_name" "group_name" +createRecursiveDir(){ + local rootDir=$1 + local pathDirs=$2 + local user=$3 + local group=${4:-${user}} + local fullPath= + + [ ! -z "${rootDir}" ] || return 0 + + createDir "${rootDir}" "${user}" "${group}" + + [ ! -z "${pathDirs}" ] || return 0 + + fullPath=${rootDir} + + for dir in ${pathDirs}; do + fullPath=${fullPath}/${dir} + createDir "${fullPath}" "${user}" "${group}" + done +} + +# Utility method to create a directory +# Failure conditions: +## Exits if unable to create a directory +# Parameters: +## $1: directory to create +## $2: user who should own the directory. Optional +## $3: group who should own the directory. Optional +# Depends on global: none +# Updates global: none +# Returns: NA + +createDir(){ + local dirName="$1" + local printMessage=no + logSilly "Method ${FUNCNAME[0]} invoked with [$dirName]" + [ -z "${dirName}" ] && return + + logDebug "Attempting to create ${dirName}" + mkdir -p "${dirName}" || errorExit "Unable to create directory: [${dirName}]" + local userID="$2" + local groupID=${3:-$userID} + + # If UID/GID is passed, chown the folder + if [ ! -z "$userID" ] && [ ! -z "$groupID" ]; then + # Earlier, this line would have returned 1 if it failed. Now it just warns. + # This is intentional. Earlier, this line would NOT be reached if the folder already existed. + # Since it will always come to this line and the script may be running as a non-root user, this method will just warn if + # setting permissions fails (so as to not affect any existing flows) + io_setOwnershipNonRecursive "$dirName" "$userID" "$groupID" || warn "Could not set owner of [$dirName] to [$userID:$groupID]" + fi + # logging message to print created dir with user and group + local logMessage=${4:-$printMessage} + if [[ "${logMessage}" == "yes" ]]; then + logger "Successfully created directory [${dirName}]. Owner: [${userID}:${groupID}]" + fi +} + +removeSoftLinkAndCreateDir () { + local dirName="$1" + local userID="$2" + local groupID="$3" + local logMessage="$4" + removeSoftLink "${dirName}" + createDir "${dirName}" "${userID}" "${groupID}" "${logMessage}" +} + +# Utility method to remove a soft link +removeSoftLink () { + local dirName="$1" + if [[ -L "${dirName}" ]]; then + targetLink=$(readlink -f "${dirName}") + logger "Removing the symlink [${dirName}] pointing to [${targetLink}]" + rm -f "${dirName}" + fi +} + +# Check Directory exist in the path +checkDirExists () { + local directoryPath="$1" + + [[ -d "${directoryPath}" ]] && echo -n "true" || echo -n "false" +} + + +# Utility method to create a file +# Failure conditions: +# Parameters: +## $1: file to create +# Depends on global: none +# Updates global: none +# Returns: NA + +createFile(){ + local fileName="$1" + logSilly "Method ${FUNCNAME[0]} [$fileName]" + [ -f "${fileName}" ] && return 0 + touch "${fileName}" || return 1 + + local userID="$2" + local groupID=${3:-$userID} + + # If UID/GID is passed, chown the folder + if [ ! -z "$userID" ] && [ ! -z "$groupID" ]; then + io_setOwnership "$fileName" "$userID" "$groupID" || return 1 + fi +} + +# Check File exist in the filePath +# IMPORTANT- DON'T ADD LOGGING to this method +checkFileExists () { + local filePath="$1" + + [[ -f "${filePath}" ]] && echo -n "true" || echo -n "false" +} + +# Check for directories contains any (files or sub directories) +# IMPORTANT- DON'T ADD LOGGING to this method +checkDirContents () { + local directoryPath="$1" + if [[ "$(ls -1 "${directoryPath}" | wc -l)" -gt 0 ]]; then + echo -n "true" + else + echo -n "false" + fi +} + +# Check contents exist in directory +# IMPORTANT- DON'T ADD LOGGING to this method +checkContentExists () { + local source="$1" + + if [[ "$(checkDirContents "${source}")" != "true" ]]; then + echo -n "false" + else + echo -n "true" + fi +} + +# Resolve the variable +# IMPORTANT- DON'T ADD LOGGING to this method +evalVariable () { + local output="$1" + local input="$2" + + eval "${output}"=\${"${input}"} + eval echo \${"${output}"} +} + +# Usage: if [ "$(io_commandExists 'curl')" == "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_commandExists() { + local commandToExecute="$1" + hash "${commandToExecute}" 2>/dev/null + local rt=$? + if [ "$rt" == 0 ]; then echo -n "yes"; else echo -n "no"; fi +} + +# Usage: if [ "$(io_curlExists)" != "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_curlExists() { + io_commandExists "curl" +} + + +io_hasMatch() { + logSilly "Method ${FUNCNAME[0]}" + local result=0 + logDebug "Executing [echo \"$1\" | grep \"$2\" >/dev/null 2>&1]" + echo "$1" | grep "$2" >/dev/null 2>&1 || result=1 + return $result +} + +# Utility method to check if the string passed (usually a connection url) corresponds to this machine itself +# Failure conditions: None +# Parameters: +## $1: string to check against +# Depends on global: none +# Updates global: IS_LOCALHOST with value "yes/no" +# Returns: NA + +io_getIsLocalhost() { + logSilly "Method ${FUNCNAME[0]}" + IS_LOCALHOST="$FLAG_N" + local inputString="$1" + logDebug "Parsing [$inputString] to check if we are dealing with this machine itself" + + io_hasMatch "$inputString" "localhost" && { + logDebug "Found localhost. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for localhost" + + local hostIP=$(io_getPublicHostIP) + io_hasMatch "$inputString" "$hostIP" && { + logDebug "Found $hostIP. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostIP" + + local hostID=$(io_getPublicHostID) + io_hasMatch "$inputString" "$hostID" && { + logDebug "Found $hostID. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostID" + + local hostName=$(io_getPublicHostName) + io_hasMatch "$inputString" "$hostName" && { + logDebug "Found $hostName. Returning [$FLAG_Y]" + IS_LOCALHOST="$FLAG_Y" && return; + } || logDebug "Did not find match for $hostName" + +} + +# Usage: if [ "$(io_tarExists)" != "yes" ] +# IMPORTANT- DON'T ADD LOGGING to this method +io_tarExists() { + io_commandExists "tar" +} + +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostIP() { + local OS_TYPE=$(uname) + local publicHostIP= + if [ "${OS_TYPE}" == "Darwin" ]; then + ipStatus=$(ifconfig en0 | grep "status" | awk '{print$2}') + if [ "${ipStatus}" == "active" ]; then + publicHostIP=$(ifconfig en0 | grep inet | grep -v inet6 | awk '{print $2}') + else + errorExit "Host IP could not be resolved!" + fi + elif [ "${OS_TYPE}" == "Linux" ]; then + publicHostIP=$(hostname -i 2>/dev/null || echo "127.0.0.1") + fi + publicHostIP=$(echo "${publicHostIP}" | awk '{print $1}') + echo -n "${publicHostIP}" +} + +# Will return the short host name (up to the first dot) +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostName() { + echo -n "$(hostname -s)" +} + +# Will return the full host name (use this as much as possible) +# IMPORTANT- DON'T ADD LOGGING to this method +io_getPublicHostID() { + echo -n "$(hostname)" +} + +# Utility method to backup a file +# Failure conditions: NA +# Parameters: filePath +# Depends on global: none, +# Updates global: none +# Returns: NA +io_backupFile() { + logSilly "Method ${FUNCNAME[0]}" + fileName="$1" + if [ ! -f "${filePath}" ]; then + logDebug "No file: [${filePath}] to backup" + return + fi + dateTime=$(date +"%Y-%m-%d-%H-%M-%S") + targetFileName="${fileName}.backup.${dateTime}" + yes | \cp -f "$fileName" "${targetFileName}" + logger "File [${fileName}] backedup as [${targetFileName}]" +} + +# Reference https://stackoverflow.com/questions/4023830/how-to-compare-two-strings-in-dot-separated-version-format-in-bash/4025065#4025065 +is_number() { + case "$BASH_VERSION" in + 3.1.*) + PATTERN='\^\[0-9\]+\$' + ;; + *) + PATTERN='^[0-9]+$' + ;; + esac + + [[ "$1" =~ $PATTERN ]] +} + +io_compareVersions() { + if [[ $# != 2 ]] + then + echo "Usage: min_version current minimum" + return + fi + + A="${1%%.*}" + B="${2%%.*}" + + if [[ "$A" != "$1" && "$B" != "$2" && "$A" == "$B" ]] + then + io_compareVersions "${1#*.}" "${2#*.}" + else + if is_number "$A" && is_number "$B" + then + if [[ "$A" -eq "$B" ]]; then + echo "0" + elif [[ "$A" -gt "$B" ]]; then + echo "1" + elif [[ "$A" -lt "$B" ]]; then + echo "-1" + fi + fi + fi +} + +# Reference https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-a-bash-variable +# Strip all leading and trailing spaces +# IMPORTANT- DON'T ADD LOGGING to this method +io_trim() { + local var="$1" + # remove leading whitespace characters + var="${var#"${var%%[![:space:]]*}"}" + # remove trailing whitespace characters + var="${var%"${var##*[![:space:]]}"}" + echo -n "$var" +} + +# temporary function will be removing it ASAP +# search for string and replace text in file +replaceText_migration_hook () { + local regexString="$1" + local replaceText="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e "s/${regexString}/${replaceText}/" "${file}" || warn "Failed to replace the text in ${file}" + else + sed -i -e "s/${regexString}/${replaceText}/" "${file}" || warn "Failed to replace the text in ${file}" + fi +} + +# search for string and replace text in file +replaceText () { + local regexString="$1" + local replaceText="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e "s#${regexString}#${replaceText}#" "${file}" || warn "Failed to replace the text in ${file}" + else + sed -i -e "s#${regexString}#${replaceText}#" "${file}" || warn "Failed to replace the text in ${file}" + logDebug "Replaced [$regexString] with [$replaceText] in [$file]" + fi +} + +# search for string and prepend text in file +prependText () { + local regexString="$1" + local text="$2" + local file="$3" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e '/'"${regexString}"'/i\'$'\n\\'"${text}"''$'\n' "${file}" || warn "Failed to prepend the text in ${file}" + else + sed -i -e '/'"${regexString}"'/i\'$'\n\\'"${text}"''$'\n' "${file}" || warn "Failed to prepend the text in ${file}" + fi +} + +# add text to beginning of the file +addText () { + local text="$1" + local file="$2" + + if [[ "$(checkFileExists "${file}")" != "true" ]]; then + return + fi + if [[ $(uname) == "Darwin" ]]; then + sed -i '' -e '1s/^/'"${text}"'\'$'\n/' "${file}" || warn "Failed to add the text in ${file}" + else + sed -i -e '1s/^/'"${text}"'\'$'\n/' "${file}" || warn "Failed to add the text in ${file}" + fi +} + +io_replaceString () { + local value="$1" + local firstString="$2" + local secondString="$3" + local separator=${4:-"/"} + local updateValue= + if [[ $(uname) == "Darwin" ]]; then + updateValue=$(echo "${value}" | sed "s${separator}${firstString}${separator}${secondString}${separator}") + else + updateValue=$(echo "${value}" | sed "s${separator}${firstString}${separator}${secondString}${separator}") + fi + echo -n "${updateValue}" +} + +_findYQ() { + # logSilly "Method ${FUNCNAME[0]}" (Intentionally not logging. Does not add value) + local parentDir="$1" + if [ -z "$parentDir" ]; then + return + fi + logDebug "Executing command [find "${parentDir}" -name third-party -type d]" + local yq=$(find "${parentDir}" -name third-party -type d) + if [ -d "${yq}/yq" ]; then + export YQ_PATH="${yq}/yq" + fi +} + + +io_setYQPath() { + # logSilly "Method ${FUNCNAME[0]}" (Intentionally not logging. Does not add value) + if [ "$(io_commandExists 'yq')" == "yes" ]; then + return + fi + + if [ ! -z "${JF_PRODUCT_HOME}" ] && [ -d "${JF_PRODUCT_HOME}" ]; then + _findYQ "${JF_PRODUCT_HOME}" + fi + + if [ -z "${YQ_PATH}" ] && [ ! -z "${COMPOSE_HOME}" ] && [ -d "${COMPOSE_HOME}" ]; then + _findYQ "${COMPOSE_HOME}" + fi + # TODO We can remove this block after all the code is restructured. + if [ -z "${YQ_PATH}" ] && [ ! -z "${SCRIPT_HOME}" ] && [ -d "${SCRIPT_HOME}" ]; then + _findYQ "${SCRIPT_HOME}" + fi + +} + +io_getLinuxDistribution() { + LINUX_DISTRIBUTION= + + # Make sure running on Linux + [ $(uname -s) != "Linux" ] && return + + # Find out what Linux distribution we are on + + cat /etc/*-release | grep -i Red >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat || true + + # OS 6.x + cat /etc/issue.net | grep Red >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat || true + + # OS 7.x + cat /etc/*-release | grep -i centos >/dev/null 2>&1 && LINUX_DISTRIBUTION=CentOS && LINUX_DISTRIBUTION_VER="7" || true + + # OS 8.x + grep -q -i "release 8" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="8" || true + + # OS 7.x + grep -q -i "release 7" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="7" || true + + # OS 6.x + grep -q -i "release 6" /etc/redhat-release >/dev/null 2>&1 && LINUX_DISTRIBUTION_VER="6" || true + + cat /etc/*-release | grep -i Red | grep -i 'VERSION=7' >/dev/null 2>&1 && LINUX_DISTRIBUTION=RedHat && LINUX_DISTRIBUTION_VER="7" || true + + cat /etc/*-release | grep -i debian >/dev/null 2>&1 && LINUX_DISTRIBUTION=Debian || true + + cat /etc/*-release | grep -i ubuntu >/dev/null 2>&1 && LINUX_DISTRIBUTION=Ubuntu || true +} + +## Utility method to check ownership of folders/files +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If file is not owned by the user & group +## Parameters: + ## user + ## group + ## folder to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac +io_checkOwner () { + logSilly "Method ${FUNCNAME[0]}" + local osType=$(uname) + + if [ "${osType}" != "Linux" ]; then + logDebug "Unsupported OS. Skipping check" + return 0 + fi + + local file_to_check=$1 + local user_id_to_check=$2 + + + if [ -z "$user_id_to_check" ] || [ -z "$file_to_check" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group_id_to_check=${3:-$user_id_to_check} + local check_user_name=${4:-"no"} + + logDebug "Checking permissions on [$file_to_check] for user [$user_id_to_check] & group [$group_id_to_check]" + + local stat= + + if [ "${check_user_name}" == "yes" ]; then + stat=( $(stat -Lc "%U %G" ${file_to_check}) ) + else + stat=( $(stat -Lc "%u %g" ${file_to_check}) ) + fi + + local user_id=${stat[0]} + local group_id=${stat[1]} + + if [[ "${user_id}" != "${user_id_to_check}" ]] || [[ "${group_id}" != "${group_id_to_check}" ]] ; then + logDebug "Ownership mismatch. [${file_to_check}] is not owned by [${user_id_to_check}:${group_id_to_check}]" + return 1 + else + return 0 + fi +} + +## Utility method to change ownership of a file/folder - NON recursive +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If chown operation fails - returns 1 +## Parameters: + ## user + ## group + ## file to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac + +io_setOwnershipNonRecursive() { + + local osType=$(uname) + if [ "${osType}" != "Linux" ]; then + return + fi + + local targetFile=$1 + local user=$2 + + if [ -z "$user" ] || [ -z "$targetFile" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group=${3:-$user} + logDebug "Method ${FUNCNAME[0]}. Executing [chown ${user}:${group} ${targetFile}]" + chown ${user}:${group} ${targetFile} || return 1 +} + +## Utility method to change ownership of a file. +## IMPORTANT +## If being called on a folder, should ONLY be called for fresh folders or may cause performance issues +## Failure conditions: + ## If invoked with incorrect inputs - FATAL + ## If chown operation fails - returns 1 +## Parameters: + ## user + ## group + ## file to chown +## Globals: none +## Returns: none +## NOTE: The method does NOTHING if the OS is Mac + +io_setOwnership() { + + local osType=$(uname) + if [ "${osType}" != "Linux" ]; then + return + fi + + local targetFile=$1 + local user=$2 + + if [ -z "$user" ] || [ -z "$targetFile" ]; then + errorExit "Invalid invocation of method. Missing mandatory inputs" + fi + + local group=${3:-$user} + logDebug "Method ${FUNCNAME[0]}. Executing [chown -R ${user}:${group} ${targetFile}]" + chown -R ${user}:${group} ${targetFile} || return 1 +} + +## Utility method to create third party folder structure necessary for Postgres +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## POSTGRESQL_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createPostgresDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${POSTGRESQL_DATA_ROOT}" ] && return 0 + + logDebug "Property [${POSTGRESQL_DATA_ROOT}] exists. Proceeding" + + createDir "${POSTGRESQL_DATA_ROOT}/data" + io_setOwnership "${POSTGRESQL_DATA_ROOT}" "${POSTGRES_USER}" "${POSTGRES_USER}" || errorExit "Setting ownership of [${POSTGRESQL_DATA_ROOT}] to [${POSTGRES_USER}:${POSTGRES_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Nginx +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## NGINX_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createNginxDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${NGINX_DATA_ROOT}" ] && return 0 + + logDebug "Property [${NGINX_DATA_ROOT}] exists. Proceeding" + + createDir "${NGINX_DATA_ROOT}" + io_setOwnership "${NGINX_DATA_ROOT}" "${NGINX_USER}" "${NGINX_GROUP}" || errorExit "Setting ownership of [${NGINX_DATA_ROOT}] to [${NGINX_USER}:${NGINX_GROUP}] failed" +} + +## Utility method to create third party folder structure necessary for ElasticSearch +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## ELASTIC_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createElasticSearchDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${ELASTIC_DATA_ROOT}" ] && return 0 + + logDebug "Property [${ELASTIC_DATA_ROOT}] exists. Proceeding" + + createDir "${ELASTIC_DATA_ROOT}/data" + io_setOwnership "${ELASTIC_DATA_ROOT}" "${ES_USER}" "${ES_USER}" || errorExit "Setting ownership of [${ELASTIC_DATA_ROOT}] to [${ES_USER}:${ES_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Redis +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## REDIS_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createRedisDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${REDIS_DATA_ROOT}" ] && return 0 + + logDebug "Property [${REDIS_DATA_ROOT}] exists. Proceeding" + + createDir "${REDIS_DATA_ROOT}" + io_setOwnership "${REDIS_DATA_ROOT}" "${REDIS_USER}" "${REDIS_USER}" || errorExit "Setting ownership of [${REDIS_DATA_ROOT}] to [${REDIS_USER}:${REDIS_USER}] failed" +} + +## Utility method to create third party folder structure necessary for Mongo +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## MONGODB_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createMongoDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${MONGODB_DATA_ROOT}" ] && return 0 + + logDebug "Property [${MONGODB_DATA_ROOT}] exists. Proceeding" + + createDir "${MONGODB_DATA_ROOT}/logs" + createDir "${MONGODB_DATA_ROOT}/configdb" + createDir "${MONGODB_DATA_ROOT}/db" + io_setOwnership "${MONGODB_DATA_ROOT}" "${MONGO_USER}" "${MONGO_USER}" || errorExit "Setting ownership of [${MONGODB_DATA_ROOT}] to [${MONGO_USER}:${MONGO_USER}] failed" +} + +## Utility method to create third party folder structure necessary for RabbitMQ +## Failure conditions: +## If creation of directory or assigning permissions fails +## Parameters: none +## Globals: +## RABBITMQ_DATA_ROOT +## Returns: none +## NOTE: The method does NOTHING if the folder already exists +io_createRabbitMQDir() { + logDebug "Method ${FUNCNAME[0]}" + [ -z "${RABBITMQ_DATA_ROOT}" ] && return 0 + + logDebug "Property [${RABBITMQ_DATA_ROOT}] exists. Proceeding" + + createDir "${RABBITMQ_DATA_ROOT}" + io_setOwnership "${RABBITMQ_DATA_ROOT}" "${RABBITMQ_USER}" "${RABBITMQ_USER}" || errorExit "Setting ownership of [${RABBITMQ_DATA_ROOT}] to [${RABBITMQ_USER}:${RABBITMQ_USER}] failed" +} + +# Add or replace a property in provided properties file +addOrReplaceProperty() { + local propertyName=$1 + local propertyValue=$2 + local propertiesPath=$3 + local delimiter=${4:-"="} + + # Return if any of the inputs are empty + [[ -z "$propertyName" || "$propertyName" == "" ]] && return + [[ -z "$propertyValue" || "$propertyValue" == "" ]] && return + [[ -z "$propertiesPath" || "$propertiesPath" == "" ]] && return + + grep "^${propertyName}\s*${delimiter}.*$" ${propertiesPath} > /dev/null 2>&1 + [ $? -ne 0 ] && echo -e "\n${propertyName}${delimiter}${propertyValue}" >> ${propertiesPath} + sed -i -e "s|^${propertyName}\s*${delimiter}.*$|${propertyName}${delimiter}${propertyValue}|g;" ${propertiesPath} +} + +# Set property only if its not set +io_setPropertyNoOverride(){ + local propertyName=$1 + local propertyValue=$2 + local propertiesPath=$3 + + # Return if any of the inputs are empty + [[ -z "$propertyName" || "$propertyName" == "" ]] && return + [[ -z "$propertyValue" || "$propertyValue" == "" ]] && return + [[ -z "$propertiesPath" || "$propertiesPath" == "" ]] && return + + grep "^${propertyName}:" ${propertiesPath} > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo -e "${propertyName}: ${propertyValue}" >> ${propertiesPath} || warn "Setting property ${propertyName}: ${propertyValue} in [ ${propertiesPath} ] failed" + else + logger "Skipping update of property : ${propertyName}" >&6 + fi +} + +# Add a line to a file if it doesn't already exist +addLine() { + local line_to_add=$1 + local target_file=$2 + logger "Trying to add line $1 to $2" >&6 2>&1 + cat "$target_file" | grep -F "$line_to_add" -wq >&6 2>&1 + if [ $? != 0 ]; then + logger "Line does not exist and will be added" >&6 2>&1 + echo $line_to_add >> $target_file || errorExit "Could not update $target_file" + fi +} + +# Utility method to check if a value (first parameter) exists in an array (2nd parameter) +# 1st parameter "value to find" +# 2nd parameter "The array to search in. Please pass a string with each value separated by space" +# Example: containsElement "y" "y Y n N" +containsElement () { + local searchElement=$1 + local searchArray=($2) + local found=1 + for elementInIndex in "${searchArray[@]}";do + if [[ $elementInIndex == $searchElement ]]; then + found=0 + fi + done + return $found +} + +# Utility method to get user's choice +# 1st parameter "what to ask the user" +# 2nd parameter "what choices to accept, separated by spaces" +# 3rd parameter "what is the default choice (to use if the user simply presses Enter)" +# Example 'getUserChoice "Are you feeling lucky? Punk!" "y n Y N" "y"' +getUserChoice(){ + configureLogOutput + read_timeout=${read_timeout:-0.5} + local choice="na" + local text_to_display=$1 + local choices=$2 + local default_choice=$3 + users_choice= + + until containsElement "$choice" "$choices"; do + echo "";echo ""; + sleep $read_timeout #This ensures correct placement of the question. + read -p "$text_to_display :" choice + : ${choice:=$default_choice} + done + users_choice=$choice + echo -e "\n$text_to_display: $users_choice" >&6 + sleep $read_timeout #This ensures correct logging +} + +setFilePermission () { + local permission=$1 + local file=$2 + chmod "${permission}" "${file}" || warn "Setting permission ${permission} to file [ ${file} ] failed" +} + + +#setting required paths +setAppDir (){ + SCRIPT_DIR=$(dirname $0) + SCRIPT_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + APP_DIR="`cd "${SCRIPT_HOME}";pwd`" +} + +ZIP_TYPE="zip" +COMPOSE_TYPE="compose" +HELM_TYPE="helm" +RPM_TYPE="rpm" +DEB_TYPE="debian" + +sourceScript () { + local file="$1" + + [ ! -z "${file}" ] || errorExit "target file is not passed to source a file" + + if [ ! -f "${file}" ]; then + errorExit "${file} file is not found" + else + source "${file}" || errorExit "Unable to source ${file}, please check if the user ${USER} has permissions to perform this action" + fi +} +# Source required helpers +initHelpers () { + local systemYamlHelper="${APP_DIR}/systemYamlHelper.sh" + local thirdPartyDir=$(find ${APP_DIR}/.. -name third-party -type d) + export YQ_PATH="${thirdPartyDir}/yq" + LIBXML2_PATH="${thirdPartyDir}/libxml2/bin/xmllint" + export LD_LIBRARY_PATH="${thirdPartyDir}/libxml2/lib" + sourceScript "${systemYamlHelper}" +} +# Check migration info yaml file available in the path +checkMigrationInfoYaml () { + + if [[ -f "${APP_DIR}/migrationHelmInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationHelmInfo.yaml" + INSTALLER="${HELM_TYPE}" + elif [[ -f "${APP_DIR}/migrationZipInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationZipInfo.yaml" + INSTALLER="${ZIP_TYPE}" + elif [[ -f "${APP_DIR}/migrationRpmInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationRpmInfo.yaml" + INSTALLER="${RPM_TYPE}" + elif [[ -f "${APP_DIR}/migrationDebInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationDebInfo.yaml" + INSTALLER="${DEB_TYPE}" + elif [[ -f "${APP_DIR}/migrationComposeInfo.yaml" ]]; then + MIGRATION_SYSTEM_YAML_INFO="${APP_DIR}/migrationComposeInfo.yaml" + INSTALLER="${COMPOSE_TYPE}" + else + errorExit "File migration Info yaml does not exist in [${APP_DIR}]" + fi +} + +retrieveYamlValue () { + local yamlPath="$1" + local value="$2" + local output="$3" + local message="$4" + + [[ -z "${yamlPath}" ]] && errorExit "yamlPath is mandatory to get value from ${MIGRATION_SYSTEM_YAML_INFO}" + + getYamlValue "${yamlPath}" "${MIGRATION_SYSTEM_YAML_INFO}" "false" + value="${YAML_VALUE}" + if [[ -z "${value}" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "Empty value for ${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + elif [[ "${output}" == "Skip" ]]; then + return + else + errorExit "${message}" + fi + fi +} + +checkEnv () { + + if [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + # check Environment JF_PRODUCT_HOME is set before migration + NEW_DATA_DIR="$(evalVariable "NEW_DATA_DIR" "JF_PRODUCT_HOME")" + if [[ -z "${NEW_DATA_DIR}" ]]; then + errorExit "Environment variable JF_PRODUCT_HOME is not set, this is required to perform Migration" + fi + # appending var directory to $JF_PRODUCT_HOME + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + elif [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + getCustomDataDir_hook + NEW_DATA_DIR="${OLD_DATA_DIR}" + if [[ -z "${NEW_DATA_DIR}" ]] && [[ -z "${OLD_DATA_DIR}" ]]; then + errorExit "Could not find ${PROMPT_DATA_DIR_LOCATION} to perform Migration" + fi + else + # check Environment JF_ROOT_DATA_DIR is set before migration + OLD_DATA_DIR="$(evalVariable "OLD_DATA_DIR" "JF_ROOT_DATA_DIR")" + # check Environment JF_ROOT_DATA_DIR is set before migration + NEW_DATA_DIR="$(evalVariable "NEW_DATA_DIR" "JF_ROOT_DATA_DIR")" + if [[ -z "${NEW_DATA_DIR}" ]] && [[ -z "${OLD_DATA_DIR}" ]]; then + errorExit "Could not find ${PROMPT_DATA_DIR_LOCATION} to perform Migration" + fi + # appending var directory to $JF_PRODUCT_HOME + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + fi + +} + +getDataDir () { + + if [[ "${INSTALLER}" == "${ZIP_TYPE}" || "${INSTALLER}" == "${COMPOSE_TYPE}"|| "${INSTALLER}" == "${HELM_TYPE}" ]]; then + checkEnv + else + getCustomDataDir_hook + NEW_DATA_DIR="`cd "${APP_DIR}"/../../;pwd`" + NEW_DATA_DIR="${NEW_DATA_DIR}/var" + fi +} + +# Retrieve Product name from MIGRATION_SYSTEM_YAML_INFO +getProduct () { + retrieveYamlValue "migration.product" "${YAML_VALUE}" "Fail" "Empty value under ${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + PRODUCT="${YAML_VALUE}" + PRODUCT=$(echo "${PRODUCT}" | tr '[:upper:]' '[:lower:]' 2>/dev/null) + if [[ "${PRODUCT}" != "artifactory" && "${PRODUCT}" != "distribution" && "${PRODUCT}" != "xray" ]]; then + errorExit "migration.product in [${MIGRATION_SYSTEM_YAML_INFO}] is not correct, please set based on product as ARTIFACTORY or DISTRIBUTION" + fi + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + JF_USER="${PRODUCT}" + fi +} +# Compare product version with minProductVersion and maxProductVersion +migrateCheckVersion () { + local productVersion="$1" + local minProductVersion="$2" + local maxProductVersion="$3" + local productVersion618="6.18.0" + local unSupportedProductVersions7=("7.2.0 7.2.1") + + if [[ "$(io_compareVersions "${productVersion}" "${maxProductVersion}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${maxProductVersion}")" -eq 1 ]]; then + logger "Migration not necessary. ${PRODUCT} is already ${productVersion}" + exit 11 + elif [[ "$(io_compareVersions "${productVersion}" "${minProductVersion}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${minProductVersion}")" -eq 1 ]]; then + if [[ ("$(io_compareVersions "${productVersion}" "${productVersion618}")" -eq 0 || "$(io_compareVersions "${productVersion}" "${productVersion618}")" -eq 1) && " ${unSupportedProductVersions7[@]} " =~ " ${CURRENT_VERSION} " ]]; then + touch /tmp/error; + errorExit "Current ${PRODUCT} version (${productVersion}) does not support migration to ${CURRENT_VERSION}" + else + bannerStart "Detected ${PRODUCT} ${productVersion}, initiating migration" + fi + else + logger "Current ${PRODUCT} ${productVersion} version is not supported for migration" + exit 1 + fi +} + +getProductVersion () { + local minProductVersion="$1" + local maxProductVersion="$2" + local newfilePath="$3" + local oldfilePath="$4" + local propertyInDocker="$5" + local property="$6" + local productVersion= + local status= + + if [[ "$INSTALLER" == "${COMPOSE_TYPE}" ]]; then + if [[ -f "${oldfilePath}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + else + productVersion="$(cat "${oldfilePath}")" + fi + status="success" + elif [[ -f "${newfilePath}" ]]; then + productVersion="$(readKey "${propertyInDocker}" "${newfilePath}")" + status="fail" + else + logger "File [${oldfilePath}] or [${newfilePath}] not found to get current version." + exit 0 + fi + elif [[ "$INSTALLER" == "${HELM_TYPE}" ]]; then + if [[ -f "${oldfilePath}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + else + productVersion="$(cat "${oldfilePath}")" + fi + status="success" + else + productVersion="${CURRENT_VERSION}" + [[ -z "${productVersion}" || "${productVersion}" == "" ]] && logger "${PRODUCT} CURRENT_VERSION is not set" && exit 0 + fi + else + if [[ -f "${newfilePath}" ]]; then + productVersion="$(readKey "${property}" "${newfilePath}")" + status="fail" + elif [[ -f "${oldfilePath}" ]]; then + productVersion="$(readKey "${property}" "${oldfilePath}")" + status="success" + else + if [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + logger "File [${newfilePath}] not found to get current version." + else + logger "File [${oldfilePath}] or [${newfilePath}] not found to get current version." + fi + exit 0 + fi + fi + if [[ -z "${productVersion}" || "${productVersion}" == "" ]]; then + [[ "${status}" == "success" ]] && logger "No version found in file [${oldfilePath}]." + [[ "${status}" == "fail" ]] && logger "No version found in file [${newfilePath}]." + exit 0 + fi + + migrateCheckVersion "${productVersion}" "${minProductVersion}" "${maxProductVersion}" +} + +readKey () { + local property="$1" + local file="$2" + local version= + + while IFS='=' read -r key value || [ -n "${key}" ]; + do + [[ ! "${key}" =~ \#.* && ! -z "${key}" && ! -z "${value}" ]] + key="$(io_trim "${key}")" + if [[ "${key}" == "${property}" ]]; then + version="${value}" && check=true && break + else + check=false + fi + done < "${file}" + if [[ "${check}" == "false" ]]; then + return + fi + echo "${version}" +} + +# create Log directory +createLogDir () { + if [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" + fi +} + +# Creating migration log file +creationMigrateLog () { + local LOG_FILE_NAME="migration.log" + createLogDir + local MIGRATION_LOG_FILE="${NEW_DATA_DIR}/log/${LOG_FILE_NAME}" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + MIGRATION_LOG_FILE="${SCRIPT_HOME}/${LOG_FILE_NAME}" + fi + touch "${MIGRATION_LOG_FILE}" + setFilePermission "${LOG_FILE_PERMISSION}" "${MIGRATION_LOG_FILE}" + exec &> >(tee -a "${MIGRATION_LOG_FILE}") +} +# Set path where system.yaml should create +setSystemYamlPath () { + SYSTEM_YAML_PATH="${NEW_DATA_DIR}/etc/system.yaml" + if [[ "${INSTALLER}" != "${HELM_TYPE}" ]]; then + logger "system.yaml will be created in path [${SYSTEM_YAML_PATH}]" + fi +} +# Create directory +createDirectory () { + local directory="$1" + local output="$2" + local check=false + local message="Could not create directory ${directory}, please check if the user ${USER} has permissions to perform this action" + removeSoftLink "${directory}" + mkdir -p "${directory}" && check=true || check=false + if [[ "${check}" == "false" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "${message}" + else + errorExit "${message}" + fi + fi + setOwnershipBasedOnInstaller "${directory}" +} + +setOwnershipBasedOnInstaller () { + local directory="$1" + if [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + chown -R ${USER_TO_CHECK}:${GROUP_TO_CHECK} "${directory}" || warn "Setting ownership on $directory failed" + elif [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + io_setOwnership "${directory}" "${JF_USER}" "${JF_USER}" + fi +} + +getUserAndGroup () { + local file="$1" + read uid gid <<<$(stat -c '%U %G' ${file}) + USER_TO_CHECK="${uid}" + GROUP_TO_CHECK="${gid}" +} + +# set ownership +getUserAndGroupFromFile () { + case $PRODUCT in + artifactory) + getUserAndGroup "/etc/opt/jfrog/artifactory/artifactory.properties" + ;; + distribution) + getUserAndGroup "${OLD_DATA_DIR}/etc/versions.properties" + ;; + xray) + getUserAndGroup "${OLD_DATA_DIR}/security/master.key" + ;; + esac +} + +# creating required directories +createRequiredDirs () { + bannerSubSection "CREATING REQUIRED DIRECTORIES" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc/security" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log/archived" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/work" "${JF_USER}" "${JF_USER}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/backup" "${JF_USER}" "${JF_USER}" "yes" + io_setOwnership "${NEW_DATA_DIR}" "${JF_USER}" "${JF_USER}" + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data/postgres" "${POSTGRES_USER}" "${POSTGRES_USER}" "yes" + fi + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + getUserAndGroupFromFile + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/etc/security" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/data" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/log/archived" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/work" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + removeSoftLinkAndCreateDir "${NEW_DATA_DIR}/backup" "${USER_TO_CHECK}" "${GROUP_TO_CHECK}" "yes" + fi +} + +# Check entry in map is format +checkMapEntry () { + local entry="$1" + + [[ "${entry}" != *"="* ]] && echo -n "false" || echo -n "true" +} +# Check value Empty and warn +warnIfEmpty () { + local filePath="$1" + local yamlPath="$2" + local check= + + if [[ -z "${filePath}" ]]; then + warn "Empty value in yamlpath [${yamlPath} in [${MIGRATION_SYSTEM_YAML_INFO}]" + check=false + else + check=true + fi + echo "${check}" +} + +logCopyStatus () { + local status="$1" + local logMessage="$2" + local warnMessage="$3" + + [[ "${status}" == "success" ]] && logger "${logMessage}" + [[ "${status}" == "fail" ]] && warn "${warnMessage}" +} +# copy contents from source to destination +copyCmd () { + local source="$1" + local target="$2" + local mode="$3" + local status= + + case $mode in + unique) + cp -up "${source}"/* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied directory contents from [${source}] to [${target}]" "Failed to copy directory contents from [${source}] to [${target}]" + ;; + specific) + cp -pf "${source}" "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied file [${source}] to [${target}]" "Failed to copy file [${source}] to [${target}]" + ;; + patternFiles) + cp -pf "${source}"* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied files matching [${source}*] to [${target}]" "Failed to copy files matching [${source}*] to [${target}]" + ;; + full) + cp -prf "${source}"/* "${target}"/ && status="success" || status="fail" + logCopyStatus "${status}" "Successfully copied directory contents from [${source}] to [${target}]" "Failed to copy directory contents from [${source}] to [${target}]" + ;; + esac +} +# Check contents exist in source before copying +copyOnContentExist () { + local source="$1" + local target="$2" + local mode="$3" + + if [[ "$(checkContentExists "${source}")" == "true" ]]; then + copyCmd "${source}" "${target}" "${mode}" + else + logger "No contents to copy from [${source}]" + fi +} + +# move source to destination +moveCmd () { + local source="$1" + local target="$2" + local status= + + mv -f "${source}" "${target}" && status="success" || status="fail" + [[ "${status}" == "success" ]] && logger "Successfully moved directory [${source}] to [${target}]" + [[ "${status}" == "fail" ]] && warn "Failed to move directory [${source}] to [${target}]" +} + +# symlink target to source +symlinkCmd () { + local source="$1" + local target="$2" + local symlinkSubDir="$3" + local check=false + + if [[ "${symlinkSubDir}" == "subDir" ]]; then + ln -sf "${source}"/* "${target}" && check=true || check=false + else + ln -sf "${source}" "${target}" && check=true || check=false + fi + + [[ "${check}" == "true" ]] && logger "Successfully symlinked directory [${target}] to old [${source}]" + [[ "${check}" == "false" ]] && warn "Symlink operation failed" +} +# Check contents exist in source before symlinking +symlinkOnExist () { + local source="$1" + local target="$2" + local symlinkSubDir="$3" + + if [[ "$(checkContentExists "${source}")" == "true" ]]; then + if [[ "${symlinkSubDir}" == "subDir" ]]; then + symlinkCmd "${source}" "${target}" "subDir" + else + symlinkCmd "${source}" "${target}" + fi + else + logger "No contents to symlink from [${source}]" + fi +} + +prependDir () { + local absolutePath="$1" + local fullPath="$2" + local sourcePath= + + if [[ "${absolutePath}" = \/* ]]; then + sourcePath="${absolutePath}" + else + sourcePath="${fullPath}" + fi + echo "${sourcePath}" +} + +getFirstEntry (){ + local entry="$1" + + [[ -z "${entry}" ]] && return + echo "${entry}" | awk -F"=" '{print $1}' +} + +getSecondEntry () { + local entry="$1" + + [[ -z "${entry}" ]] && return + echo "${entry}" | awk -F"=" '{print $2}' +} +# To get absolutePath +pathResolver () { + local directoryPath="$1" + local dataDir= + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + retrieveYamlValue "migration.oldDataDir" "oldDataDir" "Warning" + dataDir="${YAML_VALUE}" + cd "${dataDir}" + else + cd "${OLD_DATA_DIR}" + fi + absoluteDir="`cd "${directoryPath}";pwd`" + echo "${absoluteDir}" +} + +checkPathResolver () { + local value="$1" + + if [[ "${value}" == \/* ]]; then + value="${value}" + else + value="$(pathResolver "${value}")" + fi + echo "${value}" +} + +propertyMigrate () { + local entry="$1" + local filePath="$2" + local fileName="$3" + local check=false + + local yamlPath="$(getFirstEntry "${entry}")" + local property="$(getSecondEntry "${entry}")" + if [[ -z "${property}" ]]; then + warn "Property is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${property}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + local keyValues=$(cat "${NEW_DATA_DIR}/${filePath}/${fileName}" | grep "^[^#]" | grep "[*=*]") + for i in ${keyValues}; do + key=$(echo "${i}" | awk -F"=" '{print $1}') + value=$(echo "${i}" | cut -f 2- -d '=') + [ -z "${key}" ] && continue + [ -z "${value}" ] && continue + if [[ "${key}" == "${property}" ]]; then + if [[ "${PRODUCT}" == "artifactory" ]]; then + value="$(migrateResolveDerbyPath "${key}" "${value}")" + value="$(migrateResolveHaDirPath "${key}" "${value}")" + if [[ "${INSTALLER}" != "${DOCKER_TYPE}" ]]; then + value="$(updatePostgresUrlString_Hook "${yamlPath}" "${value}")" + fi + fi + if [[ "${key}" == "context.url" ]]; then + local ip=$(echo "${value}" | awk -F/ '{print $3}' | sed 's/:.*//') + setSystemValue "shared.node.ip" "${ip}" "${SYSTEM_YAML_PATH}" + logger "Setting [shared.node.ip] with [${ip}] in system.yaml" + fi + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" && logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" && check=true && break || check=false + fi + done + [[ "${check}" == "false" ]] && logger "Property [${property}] not found in file [${fileName}]" +} + +setHaEnabled_hook () { + echo "" +} + +migratePropertiesFiles () { + local fileList= + local filePath= + local fileName= + local map= + + retrieveYamlValue "migration.propertyFiles.files" "fileList" "Skip" + fileList="${YAML_VALUE}" + if [[ -z "${fileList}" ]]; then + return + fi + bannerSection "PROCESSING MIGRATION OF PROPERTY FILES" + for file in ${fileList}; + do + bannerSubSection "Processing Migration of $file" + retrieveYamlValue "migration.propertyFiles.$file.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + retrieveYamlValue "migration.propertyFiles.$file.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + [[ -z "${filePath}" && -z "${fileName}" ]] && continue + if [[ "$(checkFileExists "${NEW_DATA_DIR}/${filePath}/${fileName}")" == "true" ]]; then + logger "File [${fileName}] found in path [${NEW_DATA_DIR}/${filePath}]" + # setting haEnabled with true only if ha-node.properties is present + setHaEnabled_hook "${filePath}" + retrieveYamlValue "migration.propertyFiles.$file.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + propertyMigrate "${entry}" "${filePath}" "${fileName}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=property" + fi + done + else + logger "File [${fileName}] was not found in path [${NEW_DATA_DIR}/${filePath}] to migrate" + fi + done +} + +createTargetDir () { + local mountDir="$1" + local target="$2" + + logger "Target directory not found [${mountDir}/${target}], creating it" + createDirectoryRecursive "${mountDir}" "${target}" "Warning" +} + +createDirectoryRecursive () { + local mountDir="$1" + local target="$2" + local output="$3" + local check=false + local message="Could not create directory ${directory}, please check if the user ${USER} has permissions to perform this action" + removeSoftLink "${mountDir}/${target}" + local directory=$(echo "${target}" | tr '/' ' ' ) + local targetDir="${mountDir}" + for dir in ${directory}; + do + targetDir="${targetDir}/${dir}" + mkdir -p "${targetDir}" && check=true || check=false + setOwnershipBasedOnInstaller "${targetDir}" + done + if [[ "${check}" == "false" ]]; then + if [[ "${output}" == "Warning" ]]; then + warn "${message}" + else + errorExit "${message}" + fi + fi +} + +copyOperation () { + local source="$1" + local target="$2" + local mode="$3" + local check=false + local targetDataDir= + local targetLink= + local date= + + # prepend OLD_DATA_DIR only if source is relative path + source="$(prependDir "${source}" "${OLD_DATA_DIR}/${source}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + #remove source if it is a symlink + if [[ -L "${source}" ]]; then + targetLink=$(readlink -f "${source}") + logger "Removing the symlink [${source}] pointing to [${targetLink}]" + rm -f "${source}" + source=${targetLink} + fi + if [[ "$(checkDirExists "${source}")" != "true" ]]; then + logger "Source [${source}] directory not found in path" + return + fi + if [[ "$(checkDirContents "${source}")" != "true" ]]; then + logger "No contents to copy from [${source}]" + return + fi + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyOnContentExist "${source}" "${targetDataDir}/${target}" "${mode}" +} + +copySpecificFiles () { + local source="$1" + local target="$2" + local mode="$3" + + # prepend OLD_DATA_DIR only if source is relative path + source="$(prependDir "${source}" "${OLD_DATA_DIR}/${source}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + if [[ "$(checkFileExists "${source}")" != "true" ]]; then + logger "Source file [${source}] does not exist in path" + return + fi + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyCmd "${source}" "${targetDataDir}/${target}" "${mode}" +} + +copyPatternMatchingFiles () { + local source="$1" + local target="$2" + local mode="$3" + local sourcePath="${4}" + + # prepend OLD_DATA_DIR only if source is relative path + sourcePath="$(prependDir "${sourcePath}" "${OLD_DATA_DIR}/${sourcePath}")" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + copyLogMessage "${mode}" + if [[ "$(checkDirExists "${sourcePath}")" != "true" ]]; then + logger "Source [${sourcePath}] directory not found in path" + return + fi + if ls "${sourcePath}/${source}"* 1> /dev/null 2>&1; then + if [[ "$(checkDirExists "${targetDataDir}/${target}")" != "true" ]]; then + createTargetDir "${targetDataDir}" "${target}" + fi + copyCmd "${sourcePath}/${source}" "${targetDataDir}/${target}" "${mode}" + else + logger "Source file [${sourcePath}/${source}*] does not exist in path" + fi +} + +copyLogMessage () { + local mode="$1" + case $mode in + specific) + logger "Copy file [${source}] to target [${targetDataDir}/${target}]" + ;; + patternFiles) + logger "Copy files matching [${sourcePath}/${source}*] to target [${targetDataDir}/${target}]" + ;; + full) + logger "Copy directory contents from source [${source}] to target [${targetDataDir}/${target}]" + ;; + unique) + logger "Copy directory contents from source [${source}] to target [${targetDataDir}/${target}]" + ;; + esac +} + +copyBannerMessages () { + local mode="$1" + local textMode="$2" + case $mode in + specific) + bannerSection "COPY ${textMode} FILES" + ;; + patternFiles) + bannerSection "COPY MATCHING ${textMode}" + ;; + full) + bannerSection "COPY ${textMode} DIRECTORIES CONTENTS" + ;; + unique) + bannerSection "COPY ${textMode} DIRECTORIES CONTENTS" + ;; + esac +} + +invokeCopyFunctions () { + local mode="$1" + local source="$2" + local target="$3" + + case $mode in + specific) + copySpecificFiles "${source}" "${target}" "${mode}" + ;; + patternFiles) + retrieveYamlValue "migration.${copyFormat}.sourcePath" "map" "Warning" + local sourcePath="${YAML_VALUE}" + copyPatternMatchingFiles "${source}" "${target}" "${mode}" "${sourcePath}" + ;; + full) + copyOperation "${source}" "${target}" "${mode}" + ;; + unique) + copyOperation "${source}" "${target}" "${mode}" + ;; + esac +} +# Copies contents from source directory and target directory +copyDataDirectories () { + local copyFormat="$1" + local mode="$2" + local map= + local source= + local target= + local textMode= + local targetDataDir= + local copyFormatValue= + + retrieveYamlValue "migration.${copyFormat}" "${copyFormat}" "Skip" + copyFormatValue="${YAML_VALUE}" + if [[ -z "${copyFormatValue}" ]]; then + return + fi + textMode=$(echo "${mode}" | tr '[:lower:]' '[:upper:]' 2>/dev/null) + copyBannerMessages "${mode}" "${textMode}" + retrieveYamlValue "migration.${copyFormat}.map" "map" "Warning" + map="${YAML_VALUE}" + if [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + targetDataDir="${NEW_DATA_DIR}" + else + targetDataDir="`cd "${NEW_DATA_DIR}"/../;pwd`" + fi + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + invokeCopyFunctions "${mode}" "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +invokeMoveFunctions () { + local source="$1" + local target="$2" + local sourceDataDir= + local targetBasename= + # prepend OLD_DATA_DIR only if source is relative path + sourceDataDir=$(prependDir "${source}" "${OLD_DATA_DIR}/${source}") + targetBasename=$(dirname "${target}") + logger "Moving directory source [${sourceDataDir}] to target [${NEW_DATA_DIR}/${target}]" + if [[ "$(checkDirExists "${sourceDataDir}")" != "true" ]]; then + logger "Directory [${sourceDataDir}] not found in path to move" + return + fi + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${targetBasename}")" != "true" ]]; then + createTargetDir "${NEW_DATA_DIR}" "${targetBasename}" + moveCmd "${sourceDataDir}" "${NEW_DATA_DIR}/${target}" + else + moveCmd "${sourceDataDir}" "${NEW_DATA_DIR}/tempDir" + moveCmd "${NEW_DATA_DIR}/tempDir" "${NEW_DATA_DIR}/${target}" + fi +} + +# Move source directory and target directory +moveDirectories () { + local moveDataDirectories= + local map= + local source= + local target= + + retrieveYamlValue "migration.moveDirectories" "moveDirectories" "Skip" + moveDirectories="${YAML_VALUE}" + if [[ -z "${moveDirectories}" ]]; then + return + fi + bannerSection "MOVE DIRECTORIES" + retrieveYamlValue "migration.moveDirectories.map" "map" "Warning" + map="${YAML_VALUE}" + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + invokeMoveFunctions "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +# Trim masterKey if its generated using hex 32 +trimMasterKey () { + local masterKeyDir=/opt/jfrog/artifactory/var/etc/security + local oldMasterKey=$(<${masterKeyDir}/master.key) + local oldMasterKey_Length=$(echo ${#oldMasterKey}) + local newMasterKey= + if [[ ${oldMasterKey_Length} -gt 32 ]]; then + bannerSection "TRIM MASTERKEY" + newMasterKey=$(echo ${oldMasterKey:0:32}) + cp ${masterKeyDir}/master.key ${masterKeyDir}/backup_master.key + logger "Original masterKey is backed up : ${masterKeyDir}/backup_master.key" + rm -rf ${masterKeyDir}/master.key + echo ${newMasterKey} > ${masterKeyDir}/master.key + logger "masterKey is trimmed : ${masterKeyDir}/master.key" + fi +} + +copyDirectories () { + + copyDataDirectories "copyFiles" "full" + copyDataDirectories "copyUniqueFiles" "unique" + copyDataDirectories "copySpecificFiles" "specific" + copyDataDirectories "copyPatternMatchingFiles" "patternFiles" +} + +symlinkDir () { + local source="$1" + local target="$2" + local targetDir= + local basename= + local targetParentDir= + + targetDir="$(dirname "${target}")" + if [[ "${targetDir}" == "${source}" ]]; then + # symlink the sub directories + createDirectory "${NEW_DATA_DIR}/${target}" "Warning" + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${target}")" == "true" ]]; then + symlinkOnExist "${OLD_DATA_DIR}/${source}" "${NEW_DATA_DIR}/${target}" "subDir" + basename="$(basename "${target}")" + cd "${NEW_DATA_DIR}/${target}" && rm -f "${basename}" + fi + else + targetParentDir="$(dirname "${NEW_DATA_DIR}/${target}")" + createDirectory "${targetParentDir}" "Warning" + if [[ "$(checkDirExists "${targetParentDir}")" == "true" ]]; then + symlinkOnExist "${OLD_DATA_DIR}/${source}" "${NEW_DATA_DIR}/${target}" + fi + fi +} + +symlinkOperation () { + local source="$1" + local target="$2" + local check=false + local targetLink= + local date= + + # Check if source is a link and do symlink + if [[ -L "${OLD_DATA_DIR}/${source}" ]]; then + targetLink=$(readlink -f "${OLD_DATA_DIR}/${source}") + symlinkOnExist "${targetLink}" "${NEW_DATA_DIR}/${target}" + else + # check if source is directory and do symlink + if [[ "$(checkDirExists "${OLD_DATA_DIR}/${source}")" != "true" ]]; then + logger "Source [${source}] directory not found in path to symlink" + return + fi + if [[ "$(checkDirContents "${OLD_DATA_DIR}/${source}")" != "true" ]]; then + logger "No contents found in [${OLD_DATA_DIR}/${source}] to symlink" + return + fi + if [[ "$(checkDirExists "${NEW_DATA_DIR}/${target}")" != "true" ]]; then + logger "Target directory [${NEW_DATA_DIR}/${target}] does not exist to create symlink, creating it" + symlinkDir "${source}" "${target}" + else + rm -rf "${NEW_DATA_DIR}/${target}" && check=true || check=false + [[ "${check}" == "false" ]] && warn "Failed to remove contents in [${NEW_DATA_DIR}/${target}/]" + symlinkDir "${source}" "${target}" + fi + fi +} +# Creates a symlink path - Source directory to which the symbolic link should point. +symlinkDirectories () { + local linkFiles= + local map= + local source= + local target= + + retrieveYamlValue "migration.linkFiles" "linkFiles" "Skip" + linkFiles="${YAML_VALUE}" + if [[ -z "${linkFiles}" ]]; then + return + fi + bannerSection "SYMLINK DIRECTORIES" + retrieveYamlValue "migration.linkFiles.map" "map" "Warning" + map="${YAML_VALUE}" + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + source="$(getSecondEntry "${entry}")" + target="$(getFirstEntry "${entry}")" + logger "Symlink directory [${NEW_DATA_DIR}/${target}] to old [${OLD_DATA_DIR}/${source}]" + [[ -z "${source}" ]] && warn "source value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${target}" ]] && warn "target value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + symlinkOperation "${source}" "${target}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e target=source" + fi + echo ""; + done +} + +updateConnectionString () { + local yamlPath="$1" + local value="$2" + local mongoPath="shared.mongo.url" + local rabbitmqPath="shared.rabbitMq.url" + local postgresPath="shared.database.url" + local redisPath="shared.redis.connectionString" + local mongoConnectionString="mongo.connectionString" + local sourceKey= + local hostIp=$(io_getPublicHostIP) + local hostKey= + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + # Replace @postgres:,@mongodb:,@rabbitmq:,@redis: to @{hostIp}: (Compose Installer) + hostKey="@${hostIp}:" + case $yamlPath in + ${postgresPath}) + sourceKey="@postgres:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${mongoPath}) + sourceKey="@mongodb:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${rabbitmqPath}) + sourceKey="@rabbitmq:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${redisPath}) + sourceKey="@redis:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + ${mongoConnectionString}) + sourceKey="@mongodb:" + value=$(io_replaceString "${value}" "${sourceKey}" "${hostKey}") + ;; + esac + fi + echo -n "${value}" +} + +yamlMigrate () { + local entry="$1" + local sourceFile="$2" + local value= + local yamlPath= + local key= + yamlPath="$(getFirstEntry "${entry}")" + key="$(getSecondEntry "${entry}")" + if [[ -z "${key}" ]]; then + warn "key is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${key}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + getYamlValue "${key}" "${sourceFile}" "false" + value="${YAML_VALUE}" + if [[ ! -z "${value}" ]]; then + value=$(updateConnectionString "${yamlPath}" "${value}") + fi + if [[ -z "${value}" ]]; then + logger "No value for [${key}] in [${sourceFile}]" + else + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the key [${key}] in system.yaml" + fi +} + +migrateYamlFile () { + local files= + local filePath= + local fileName= + local sourceFile= + local map= + retrieveYamlValue "migration.yaml.files" "files" "Skip" + files="${YAML_VALUE}" + if [[ -z "${files}" ]]; then + return + fi + bannerSection "MIGRATION OF YAML FILES" + for file in $files; + do + bannerSubSection "Processing Migration of $file" + retrieveYamlValue "migration.yaml.$file.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + retrieveYamlValue "migration.yaml.$file.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + [[ -z "${filePath}" && -z "${fileName}" ]] && continue + sourceFile="${NEW_DATA_DIR}/${filePath}/${fileName}" + if [[ "$(checkFileExists "${sourceFile}")" == "true" ]]; then + logger "File [${fileName}] found in path [${NEW_DATA_DIR}/${filePath}]" + retrieveYamlValue "migration.yaml.$file.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + yamlMigrate "${entry}" "${sourceFile}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=key" + fi + done + else + logger "File [${fileName}] is not found in path [${NEW_DATA_DIR}/${filePath}] to migrate" + fi + done +} +# updates the key and value in system.yaml +updateYamlKeyValue () { + local entry="$1" + local value= + local yamlPath= + local key= + + yamlPath="$(getFirstEntry "${entry}")" + value="$(getSecondEntry "${entry}")" + if [[ -z "${value}" ]]; then + warn "value is empty in map [${entry}] in the file [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + if [[ -z "${yamlPath}" ]]; then + warn "yamlPath is empty for [${key}] in [${MIGRATION_SYSTEM_YAML_INFO}]" + return + fi + setSystemValue "${yamlPath}" "${value}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value [${value}] in system.yaml" +} + +updateSystemYamlFile () { + local updateYaml= + local map= + + retrieveYamlValue "migration.updateSystemYaml" "updateYaml" "Skip" + updateSystemYaml="${YAML_VALUE}" + if [[ -z "${updateSystemYaml}" ]]; then + return + fi + bannerSection "UPDATE SYSTEM YAML FILE WITH KEY AND VALUES" + retrieveYamlValue "migration.updateSystemYaml.map" "map" "Warning" + map="${YAML_VALUE}" + if [[ -z "${map}" ]]; then + return + fi + for entry in $map; + do + if [[ "$(checkMapEntry "${entry}")" == "true" ]]; then + updateYamlKeyValue "${entry}" + else + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e yamlPath=key" + fi + done +} + +backupFiles_hook () { + logSilly "Method ${FUNCNAME[0]}" +} + +backupDirectory () { + local backupDir="$1" + local dir="$2" + local targetDir="$3" + local effectiveUser= + local effectiveGroup= + + if [[ "${dir}" = \/* ]]; then + dir=$(echo "${dir/\//}") + fi + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + effectiveUser="${JF_USER}" + effectiveGroup="${JF_USER}" + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + effectiveUser="${USER_TO_CHECK}" + effectiveGroup="${GROUP_TO_CHECK}" + fi + + removeSoftLinkAndCreateDir "${backupDir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local backupDirectory="${backupDir}/${PRODUCT}" + removeSoftLinkAndCreateDir "${backupDirectory}" "${effectiveUser}" "${effectiveGroup}" "yes" + removeSoftLinkAndCreateDir "${backupDirectory}/${dir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local outputCheckDirExists="$(checkDirExists "${backupDirectory}/${dir}")" + if [[ "${outputCheckDirExists}" == "true" ]]; then + copyOnContentExist "${targetDir}" "${backupDirectory}/${dir}" "full" + fi +} + +removeOldDirectory () { + local backupDir="$1" + local entry="$2" + local check=false + + # prepend OLD_DATA_DIR only if entry is relative path + local targetDir="$(prependDir "${entry}" "${OLD_DATA_DIR}/${entry}")" + local outputCheckDirExists="$(checkDirExists "${targetDir}")" + if [[ "${outputCheckDirExists}" != "true" ]]; then + logger "No [${targetDir}] directory found to delete" + echo ""; + return + fi + backupDirectory "${backupDir}" "${entry}" "${targetDir}" + rm -rf "${targetDir}" && check=true || check=false + [[ "${check}" == "true" ]] && logger "Successfully removed directory [${targetDir}]" + [[ "${check}" == "false" ]] && warn "Failed to remove directory [${targetDir}]" + echo ""; +} + +cleanUpOldDataDirectories () { + local cleanUpOldDataDir= + local map= + local entry= + + retrieveYamlValue "migration.cleanUpOldDataDir" "cleanUpOldDataDir" "Skip" + cleanUpOldDataDir="${YAML_VALUE}" + if [[ -z "${cleanUpOldDataDir}" ]]; then + return + fi + bannerSection "CLEAN UP OLD DATA DIRECTORIES" + retrieveYamlValue "migration.cleanUpOldDataDir.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + date="$(date +%Y%m%d%H%M)" + backupDir="${NEW_DATA_DIR}/backup/backup-${date}" + bannerImportant "****** Old data configurations are backedup in [${backupDir}] directory ******" + backupFiles_hook "${backupDir}/${PRODUCT}" + for entry in $map; + do + removeOldDirectory "${backupDir}" "${entry}" + done +} + +backupFiles () { + local backupDir="$1" + local dir="$2" + local targetDir="$3" + local fileName="$4" + local effectiveUser= + local effectiveGroup= + + if [[ "${dir}" = \/* ]]; then + dir=$(echo "${dir/\//}") + fi + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" ]]; then + effectiveUser="${JF_USER}" + effectiveGroup="${JF_USER}" + elif [[ "${INSTALLER}" == "${DEB_TYPE}" || "${INSTALLER}" == "${RPM_TYPE}" ]]; then + effectiveUser="${USER_TO_CHECK}" + effectiveGroup="${GROUP_TO_CHECK}" + fi + + removeSoftLinkAndCreateDir "${backupDir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local backupDirectory="${backupDir}/${PRODUCT}" + removeSoftLinkAndCreateDir "${backupDirectory}" "${effectiveUser}" "${effectiveGroup}" "yes" + removeSoftLinkAndCreateDir "${backupDirectory}/${dir}" "${effectiveUser}" "${effectiveGroup}" "yes" + local outputCheckDirExists="$(checkDirExists "${backupDirectory}/${dir}")" + if [[ "${outputCheckDirExists}" == "true" ]]; then + copyCmd "${targetDir}/${fileName}" "${backupDirectory}/${dir}" "specific" + fi +} + +removeOldFiles () { + local backupDir="$1" + local directoryName="$2" + local fileName="$3" + local check=false + + # prepend OLD_DATA_DIR only if entry is relative path + local targetDir="$(prependDir "${directoryName}" "${OLD_DATA_DIR}/${directoryName}")" + local outputCheckFileExists="$(checkFileExists "${targetDir}/${fileName}")" + if [[ "${outputCheckFileExists}" != "true" ]]; then + logger "No [${targetDir}/${fileName}] file found to delete" + return + fi + backupFiles "${backupDir}" "${directoryName}" "${targetDir}" "${fileName}" + rm -f "${targetDir}/${fileName}" && check=true || check=false + [[ "${check}" == "true" ]] && logger "Successfully removed file [${targetDir}/${fileName}]" + [[ "${check}" == "false" ]] && warn "Failed to remove file [${targetDir}/${fileName}]" + echo ""; +} + +cleanUpOldFiles () { + local cleanUpFiles= + local map= + local entry= + + retrieveYamlValue "migration.cleanUpOldFiles" "cleanUpOldFiles" "Skip" + cleanUpOldFiles="${YAML_VALUE}" + if [[ -z "${cleanUpOldFiles}" ]]; then + return + fi + bannerSection "CLEAN UP OLD FILES" + retrieveYamlValue "migration.cleanUpOldFiles.map" "map" "Warning" + map="${YAML_VALUE}" + [[ -z "${map}" ]] && continue + date="$(date +%Y%m%d%H%M)" + backupDir="${NEW_DATA_DIR}/backup/backup-${date}" + bannerImportant "****** Old files are backedup in [${backupDir}] directory ******" + for entry in $map; + do + local outputCheckMapEntry="$(checkMapEntry "${entry}")" + if [[ "${outputCheckMapEntry}" != "true" ]]; then + warn "map entry [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}] is not in correct format, correct format i.e directoryName=fileName" + fi + local fileName="$(getSecondEntry "${entry}")" + local directoryName="$(getFirstEntry "${entry}")" + [[ -z "${fileName}" ]] && warn "File name value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + [[ -z "${directoryName}" ]] && warn "Directory name value is empty for [${entry}] in [${MIGRATION_SYSTEM_YAML_INFO}]" && continue + removeOldFiles "${backupDir}" "${directoryName}" "${fileName}" + echo ""; + done +} + +startMigration () { + bannerSection "STARTING MIGRATION" +} + +endMigration () { + bannerSection "MIGRATION COMPLETED SUCCESSFULLY" +} + +initialize () { + setAppDir + _pauseExecution "setAppDir" + initHelpers + _pauseExecution "initHelpers" + checkMigrationInfoYaml + _pauseExecution "checkMigrationInfoYaml" + getProduct + _pauseExecution "getProduct" + getDataDir + _pauseExecution "getDataDir" +} + +main () { + case $PRODUCT in + artifactory) + migrateArtifactory + ;; + distribution) + migrateDistribution + ;; + xray) + migrationXray + ;; + esac + exit 0 +} + +# Ensures meta data is logged +LOG_BEHAVIOR_ADD_META="$FLAG_Y" + + +migrateResolveDerbyPath () { + local key="$1" + local value="$2" + + if [[ "${key}" == "url" && "${value}" == *"db.home"* ]]; then + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + derbyPath="/opt/jfrog/artifactory/var/data/artifactory/derby" + value=$(echo "${value}" | sed "s|{db.home}|$derbyPath|") + else + derbyPath="${NEW_DATA_DIR}/data/artifactory/derby" + value=$(echo "${value}" | sed "s|{db.home}|$derbyPath|") + fi + fi + echo "${value}" +} + +migrateResolveHaDirPath () { + local key="$1" + local value="$2" + + if [[ "${INSTALLER}" == "${RPM_TYPE}" || "${INSTALLER}" == "${COMPOSE_TYPE}" || "${INSTALLER}" == "${HELM_TYPE}" || "${INSTALLER}" == "${DEB_TYPE}" ]]; then + if [[ "${key}" == "artifactory.ha.data.dir" || "${key}" == "artifactory.ha.backup.dir" ]]; then + value=$(checkPathResolver "${value}") + fi + fi + echo "${value}" +} +updatePostgresUrlString_Hook () { + local yamlPath="$1" + local value="$2" + local hostIp=$(io_getPublicHostIP) + local sourceKey="//postgresql:" + if [[ "${yamlPath}" == "shared.database.url" ]]; then + value=$(io_replaceString "${value}" "${sourceKey}" "//${hostIp}:" "#") + fi + echo "${value}" +} +# Check Artifactory product version +checkArtifactoryVersion () { + local minProductVersion="6.0.0" + local maxProductVersion="7.0.0" + local propertyInDocker="ARTIFACTORY_VERSION" + local property="artifactory.version" + + if [[ "${INSTALLER}" == "${COMPOSE_TYPE}" ]]; then + local newfilePath="${APP_DIR}/../.env" + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + elif [[ "${INSTALLER}" == "${HELM_TYPE}" ]]; then + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + elif [[ "${INSTALLER}" == "${ZIP_TYPE}" ]]; then + local newfilePath="${NEW_DATA_DIR}/etc/artifactory/artifactory.properties" + local oldfilePath="${OLD_DATA_DIR}/etc/artifactory.properties" + else + local newfilePath="${NEW_DATA_DIR}/etc/artifactory/artifactory.properties" + local oldfilePath="/etc/opt/jfrog/artifactory/artifactory.properties" + fi + + getProductVersion "${minProductVersion}" "${maxProductVersion}" "${newfilePath}" "${oldfilePath}" "${propertyInDocker}" "${property}" +} + +getCustomDataDir_hook () { + retrieveYamlValue "migration.oldDataDir" "oldDataDir" "Fail" + OLD_DATA_DIR="${YAML_VALUE}" +} + +# Get protocol value of connector +getXmlConnectorProtocol () { + local i="$1" + local filePath="$2" + local fileName="$3" + local protocolValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@protocol' ${filePath}/${fileName} 2>/dev/null |awk -F"=" '{print $2}' | tr -d '"') + echo -e "${protocolValue}" +} + +# Get all attributes of connector +getXmlConnectorAttributes () { + local i="$1" + local filePath="$2" + local fileName="$3" + local connectorAttributes=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@*' ${filePath}/${fileName} 2>/dev/null) + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + echo "${connectorAttributes}" +} + +# Get port value of connector +getXmlConnectorPort () { + local i="$1" + local filePath="$2" + local fileName="$3" + local portValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@port' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${portValue}" +} + +# Get maxThreads value of connector +getXmlConnectorMaxThreads () { + local i="$1" + local filePath="$2" + local fileName="$3" + local maxThreadValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@maxThreads' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${maxThreadValue}" +} +# Get sendReasonPhrase value of connector +getXmlConnectorSendReasonPhrase () { + local i="$1" + local filePath="$2" + local fileName="$3" + local sendReasonPhraseValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@sendReasonPhrase' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + echo -e "${sendReasonPhraseValue}" +} +# Get relaxedPathChars value of connector +getXmlConnectorRelaxedPathChars () { + local i="$1" + local filePath="$2" + local fileName="$3" + local relaxedPathCharsValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@relaxedPathChars' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + # strip leading and trailing spaces + relaxedPathCharsValue=$(io_trim "${relaxedPathCharsValue}") + echo -e "${relaxedPathCharsValue}" +} +# Get relaxedQueryChars value of connector +getXmlConnectorRelaxedQueryChars () { + local i="$1" + local filePath="$2" + local fileName="$3" + local relaxedQueryCharsValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@relaxedQueryChars' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + # strip leading and trailing spaces + relaxedQueryCharsValue=$(io_trim "${relaxedQueryCharsValue}") + echo -e "${relaxedQueryCharsValue}" +} + +# Updating system.yaml with Connector port +setConnectorPort () { + local yamlPath="$1" + local valuePort="$2" + local portYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${valuePort}" ]]; then + warn "port value is empty, could not migrate to system.yaml" + return + fi + ## Getting port yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" portYamlPath "Warning" + portYamlPath="${YAML_VALUE}" + if [[ -z "${portYamlPath}" ]]; then + return + fi + setSystemValue "${portYamlPath}" "${valuePort}" "${SYSTEM_YAML_PATH}" + logger "Setting [${portYamlPath}] with value [${valuePort}] in system.yaml" +} + +# Updating system.yaml with Connector maxThreads +setConnectorMaxThread () { + local yamlPath="$1" + local threadValue="$2" + local maxThreadYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${threadValue}" ]]; then + return + fi + ## Getting max Threads yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" maxThreadYamlPath "Warning" + maxThreadYamlPath="${YAML_VALUE}" + if [[ -z "${maxThreadYamlPath}" ]]; then + return + fi + setSystemValue "${maxThreadYamlPath}" "${threadValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${maxThreadYamlPath}] with value [${threadValue}] in system.yaml" +} + +# Updating system.yaml with Connector sendReasonPhrase +setConnectorSendReasonPhrase () { + local yamlPath="$1" + local sendReasonPhraseValue="$2" + local sendReasonPhraseYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${sendReasonPhraseValue}" ]]; then + return + fi + ## Getting sendReasonPhrase yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" sendReasonPhraseYamlPath "Warning" + sendReasonPhraseYamlPath="${YAML_VALUE}" + if [[ -z "${sendReasonPhraseYamlPath}" ]]; then + return + fi + setSystemValue "${sendReasonPhraseYamlPath}" "${sendReasonPhraseValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${sendReasonPhraseYamlPath}] with value [${sendReasonPhraseValue}] in system.yaml" +} + +# Updating system.yaml with Connector relaxedPathChars +setConnectorRelaxedPathChars () { + local yamlPath="$1" + local relaxedPathCharsValue="$2" + local relaxedPathCharsYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${relaxedPathCharsValue}" ]]; then + return + fi + ## Getting relaxedPathChars yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" relaxedPathCharsYamlPath "Warning" + relaxedPathCharsYamlPath="${YAML_VALUE}" + if [[ -z "${relaxedPathCharsYamlPath}" ]]; then + return + fi + setSystemValue "${relaxedPathCharsYamlPath}" "${relaxedPathCharsValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${relaxedPathCharsYamlPath}] with value [${relaxedPathCharsValue}] in system.yaml" +} + +# Updating system.yaml with Connector relaxedQueryChars +setConnectorRelaxedQueryChars () { + local yamlPath="$1" + local relaxedQueryCharsValue="$2" + local relaxedQueryCharsYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${relaxedQueryCharsValue}" ]]; then + return + fi + ## Getting relaxedQueryChars yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" relaxedQueryCharsYamlPath "Warning" + relaxedQueryCharsYamlPath="${YAML_VALUE}" + if [[ -z "${relaxedQueryCharsYamlPath}" ]]; then + return + fi + setSystemValue "${relaxedQueryCharsYamlPath}" "${relaxedQueryCharsValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${relaxedQueryCharsYamlPath}] with value [${relaxedQueryCharsValue}] in system.yaml" +} + +# Updating system.yaml with Connectors configurations +setConnectorExtraConfig () { + local yamlPath="$1" + local connectorAttributes="$2" + local extraConfigPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${connectorAttributes}" ]]; then + return + fi + ## Getting extraConfig yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" extraConfig "Warning" + extraConfigPath="${YAML_VALUE}" + if [[ -z "${extraConfigPath}" ]]; then + return + fi + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + setSystemValue "${extraConfigPath}" "${connectorAttributes}" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConfigPath}] with connector attributes in system.yaml" +} + +# Updating system.yaml with extra Connectors +setExtraConnector () { + local yamlPath="$1" + local extraConnector="$2" + local extraConnectorYamlPath= + if [[ -z "${yamlPath}" ]]; then + return + fi + if [[ -z "${extraConnector}" ]]; then + return + fi + ## Getting extraConnecotr yaml path from migration info yaml + retrieveYamlValue "${yamlPath}" extraConnectorYamlPath "Warning" + extraConnectorYamlPath="${YAML_VALUE}" + if [[ -z "${extraConnectorYamlPath}" ]]; then + return + fi + getYamlValue "${extraConnectorYamlPath}" "${SYSTEM_YAML_PATH}" "false" + local connectorExtra="${YAML_VALUE}" + if [[ -z "${connectorExtra}" ]]; then + setSystemValue "${extraConnectorYamlPath}" "${extraConnector}" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConnectorYamlPath}] with extra connectors in system.yaml" + else + setSystemValue "${extraConnectorYamlPath}" "\"${connectorExtra} ${extraConnector}\"" "${SYSTEM_YAML_PATH}" + logger "Setting [${extraConnectorYamlPath}] with extra connectors in system.yaml" + fi +} + +# Migrate extra connectors to system.yaml +migrateExtraConnectors () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local excludeDefaultPort="$4" + local i="$5" + local extraConfig= + local extraConnector= + if [[ "${excludeDefaultPort}" == "yes" ]]; then + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + [[ "${portValue}" != "${DEFAULT_ACCESS_PORT}" && "${portValue}" != "${DEFAULT_RT_PORT}" ]] || continue + extraConnector=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']' ${filePath}/${fileName} 2>/dev/null) + setExtraConnector "${EXTRA_CONFIG_YAMLPATH}" "${extraConnector}" + done + else + extraConnector=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']' ${filePath}/${fileName} 2>/dev/null) + setExtraConnector "${EXTRA_CONFIG_YAMLPATH}" "${extraConnector}" + fi +} + +# Migrate connector configurations +migrateConnectorConfig () { + local i="$1" + local protocolType="$2" + local portValue="$3" + local connectorPortYamlPath="$4" + local connectorMaxThreadYamlPath="$5" + local connectorAttributesYamlPath="$6" + local filePath="$7" + local fileName="$8" + local connectorSendReasonPhraseYamlPath="$9" + local connectorRelaxedPathCharsYamlPath="${10}" + local connectorRelaxedQueryCharsYamlPath="${11}" + + # migrate port + setConnectorPort "${connectorPortYamlPath}" "${portValue}" + + # migrate maxThreads + local maxThreadValue=$(getXmlConnectorMaxThreads "$i" "${filePath}" "${fileName}") + setConnectorMaxThread "${connectorMaxThreadYamlPath}" "${maxThreadValue}" + + # migrate sendReasonPhrase + local sendReasonPhraseValue=$(getXmlConnectorSendReasonPhrase "$i" "${filePath}" "${fileName}") + setConnectorSendReasonPhrase "${connectorSendReasonPhraseYamlPath}" "${sendReasonPhraseValue}" + + # migrate relaxedPathChars + local relaxedPathCharsValue=$(getXmlConnectorRelaxedPathChars "$i" "${filePath}" "${fileName}") + setConnectorRelaxedPathChars "${connectorRelaxedPathCharsYamlPath}" "\"${relaxedPathCharsValue}\"" + # migrate relaxedQueryChars + local relaxedQueryCharsValue=$(getXmlConnectorRelaxedQueryChars "$i" "${filePath}" "${fileName}") + setConnectorRelaxedQueryChars "${connectorRelaxedQueryCharsYamlPath}" "\"${relaxedQueryCharsValue}\"" + + # migrate all attributes to extra config except port , maxThread , sendReasonPhrase ,relaxedPathChars and relaxedQueryChars + local connectorAttributes=$(getXmlConnectorAttributes "$i" "${filePath}" "${fileName}") + connectorAttributes=$(echo "${connectorAttributes}" | sed 's/port="'${portValue}'"//g' | sed 's/maxThreads="'${maxThreadValue}'"//g' | sed 's/sendReasonPhrase="'${sendReasonPhraseValue}'"//g' | sed 's/relaxedPathChars="\'${relaxedPathCharsValue}'\"//g' | sed 's/relaxedQueryChars="\'${relaxedQueryCharsValue}'\"//g') + # strip leading and trailing spaces + connectorAttributes=$(io_trim "${connectorAttributes}") + setConnectorExtraConfig "${connectorAttributesYamlPath}" "${connectorAttributes}" +} + +# Check for default port 8040 and 8081 in connectors and migrate +migrateConnectorPort () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local defaultPort="$4" + local connectorPortYamlPath="$5" + local connectorMaxThreadYamlPath="$6" + local connectorAttributesYamlPath="$7" + local connectorSendReasonPhraseYamlPath="$8" + local connectorRelaxedPathCharsYamlPath="$9" + local connectorRelaxedQueryCharsYamlPath="${10}" + local portYamlPath= + local maxThreadYamlPath= + local status= + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" == *AJP* ]] && continue + [[ "${portValue}" != "${defaultPort}" ]] && continue + if [[ "${portValue}" == "${DEFAULT_RT_PORT}" ]]; then + RT_DEFAULTPORT_STATUS=success + else + AC_DEFAULTPORT_STATUS=success + fi + migrateConnectorConfig "${i}" "${protocolType}" "${portValue}" "${connectorPortYamlPath}" "${connectorMaxThreadYamlPath}" "${connectorAttributesYamlPath}" "${filePath}" "${fileName}" "${connectorSendReasonPhraseYamlPath}" "${connectorRelaxedPathCharsYamlPath}" "${connectorRelaxedQueryCharsYamlPath}" + done +} + +# migrate to extra, connector having default port and protocol is AJP +migrateDefaultPortIfAjp () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local defaultPort="$4" + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" != *AJP* ]] && continue + [[ "${portValue}" != "${defaultPort}" ]] && continue + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + done + +} + +# Comparing max threads in connectors +compareMaxThreads () { + local firstConnectorMaxThread="$1" + local firstConnectorNode="$2" + local secondConnectorMaxThread="$3" + local secondConnectorNode="$4" + local filePath="$5" + local fileName="$6" + + # choose higher maxThreads connector as Artifactory. + if [[ "${firstConnectorMaxThread}" -gt ${secondConnectorMaxThread} || "${firstConnectorMaxThread}" -eq ${secondConnectorMaxThread} ]]; then + # maxThread is higher in firstConnector, + # Taking firstConnector as Artifactory and SecondConnector as Access + # maxThread is equal in both connector,considering firstConnector as Artifactory and SecondConnector as Access + local rtPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + else + # maxThread is higher in SecondConnector, + # Taking SecondConnector as Artifactory and firstConnector as Access + local rtPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi +} + +# Check max threads exist to compare +maxThreadsExistToCompare () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local firstConnectorMaxThread= + local secondConnectorMaxThread= + local firstConnectorNode= + local secondConnectorNode= + local status=success + local firstnode=fail + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + if [[ ${protocolType} == *AJP* ]]; then + # Migrate Connectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + continue + fi + # store maxthreads value of each connector + if [[ ${firstnode} == "fail" ]]; then + firstConnectorMaxThread=$(getXmlConnectorMaxThreads "${i}" "${filePath}" "${fileName}") + firstConnectorNode="${i}" + firstnode=success + else + secondConnectorMaxThread=$(getXmlConnectorMaxThreads "${i}" "${filePath}" "${fileName}") + secondConnectorNode="${i}" + fi + done + [[ -z "${firstConnectorMaxThread}" ]] && status=fail + [[ -z "${secondConnectorMaxThread}" ]] && status=fail + # maxThreads is set, now compare MaxThreads + if [[ "${status}" == "success" ]]; then + compareMaxThreads "${firstConnectorMaxThread}" "${firstConnectorNode}" "${secondConnectorMaxThread}" "${secondConnectorNode}" "${filePath}" "${fileName}" + else + # Assume first connector is RT, maxThreads is not set in both connectors + local rtPortValue=$(getXmlConnectorPort "${firstConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${firstConnectorNode}" "${protocolType}" "${rtPortValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + local acPortValue=$(getXmlConnectorPort "${secondConnectorNode}" "${filePath}" "${fileName}") + migrateConnectorConfig "${secondConnectorNode}" "${protocolType}" "${acPortValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi +} + +migrateExtraBasedOnNonAjpCount () { + local nonAjpCount="$1" + local filePath="$2" + local fileName="$3" + local connectorCount="$4" + local i="$5" + + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + if [[ "${protocolType}" == *AJP* ]]; then + if [[ "${nonAjpCount}" -eq 1 ]]; then + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "no" "${i}" + continue + else + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + continue + fi + fi +} + +# find RT and AC Connector +findRtAndAcConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local initialAjpCount=0 + local nonAjpCount=0 + + # get the count of non AJP + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${protocolType}" != *AJP* ]] || continue + nonAjpCount=$((initialAjpCount+1)) + initialAjpCount="${nonAjpCount}" + done + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as access and artifactory connectors + # Mark port as 8040 for access + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + done + elif [[ "${nonAjpCount}" -eq 2 ]]; then + # compare maxThreads in both connectors + maxThreadsExistToCompare "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${nonAjpCount}" -gt 2 ]]; then + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # setting with default port in system.yaml + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +# get the count of non AJP +getCountOfNonAjp () { + local port="$1" + local connectorCount="$2" + local filePath=$3 + local fileName=$4 + local initialNonAjpCount=0 + + for ((i = 1 ; i <= "${connectorCount}" ; i++)); + do + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + local protocolType=$(getXmlConnectorProtocol "$i" "${filePath}" "${fileName}") + [[ "${portValue}" != "${port}" ]] || continue + [[ "${protocolType}" != *AJP* ]] || continue + local nonAjpCount=$((initialNonAjpCount+1)) + initialNonAjpCount="${nonAjpCount}" + done + echo -e "${nonAjpCount}" +} + +# Find for access connector +findAcConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + + # get the count of non AJP + local nonAjpCount=$(getCountOfNonAjp "${DEFAULT_RT_PORT}" "${connectorCount}" "${filePath}" "${fileName}") + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as access connector and mark port as that of connector + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" != "${DEFAULT_RT_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + fi + done + elif [[ "${nonAjpCount}" -gt 1 ]]; then + # Take RT properties into access with 8040 + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" == "${DEFAULT_RT_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + fi + done + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # Add RT connector details as access connector and mark port as 8040 + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${AC_SENDREASONPHRASE_YAMLPATH}" + setConnectorPort "${AC_PORT_YAMLPATH}" "${DEFAULT_ACCESS_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +# Find for artifactory connector +findRtConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + + # get the count of non AJP + local nonAjpCount=$(getCountOfNonAjp "${DEFAULT_ACCESS_PORT}" "${connectorCount}" "${filePath}" "${fileName}") + if [[ "${nonAjpCount}" -eq 1 ]]; then + # Add the connector found as RT connector + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" != "${DEFAULT_ACCESS_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + fi + done + elif [[ "${nonAjpCount}" -gt 1 ]]; then + # Take access properties into artifactory with 8081 + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + migrateExtraBasedOnNonAjpCount "${nonAjpCount}" "${filePath}" "${fileName}" "${connectorCount}" "$i" + local portValue=$(getXmlConnectorPort "$i" "${filePath}" "${fileName}") + if [[ "${portValue}" == "${DEFAULT_ACCESS_PORT}" ]]; then + migrateConnectorConfig "$i" "${protocolType}" "${portValue}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${filePath}" "${fileName}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + fi + done + elif [[ "${nonAjpCount}" -eq 0 ]]; then + # Add access connector details as RT connector and mark as ${DEFAULT_RT_PORT} + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + setConnectorPort "${RT_PORT_YAMLPATH}" "${DEFAULT_RT_PORT}" + # migrateExtraConnectors + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + fi +} + +checkForTlsConnector () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + for ((i = 1 ; i <= "${connectorCount}" ; i++)) + do + local sslProtocolValue=$($LIBXML2_PATH --xpath '//Server/Service/Connector['$i']/@sslProtocol' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + if [[ "${sslProtocolValue}" == "TLS" ]]; then + bannerImportant "NOTE: Ignoring TLS connector during migration, modify the system yaml to enable TLS. Original server.xml is saved in path [${filePath}/${fileName}]" + TLS_CONNECTOR_EXISTS=${FLAG_Y} + continue + fi + done +} + +# set custom tomcat server Listeners to system.yaml +setListenerConnector () { + local filePath="$1" + local fileName="$2" + local listenerCount="$3" + for ((i = 1 ; i <= "${listenerCount}" ; i++)) + do + local listenerConnector=$($LIBXML2_PATH --xpath '//Server/Listener['$i']' ${filePath}/${fileName} 2>/dev/null) + local listenerClassName=$($LIBXML2_PATH --xpath '//Server/Listener['$i']/@className' ${filePath}/${fileName} 2>/dev/null | awk -F"=" '{print $2}' | tr -d '"') + if [[ "${listenerClassName}" == *Apr* ]]; then + setExtraConnector "${EXTRA_LISTENER_CONFIG_YAMLPATH}" "${listenerConnector}" + fi + done +} +# add custom tomcat server Listeners +addTomcatServerListeners () { + local filePath="$1" + local fileName="$2" + local listenerCount="$3" + if [[ "${listenerCount}" == "0" ]]; then + logger "No listener connectors found in the [${filePath}/${fileName}],skipping migration of listener connectors" + else + setListenerConnector "${filePath}" "${fileName}" "${listenerCount}" + setSystemValue "${RT_TOMCAT_HTTPSCONNECTOR_ENABLED}" "true" "${SYSTEM_YAML_PATH}" + logger "Setting [${RT_TOMCAT_HTTPSCONNECTOR_ENABLED}] with value [true] in system.yaml" + fi +} + +# server.xml migration operations +xmlMigrateOperation () { + local filePath="$1" + local fileName="$2" + local connectorCount="$3" + local listenerCount="$4" + RT_DEFAULTPORT_STATUS=fail + AC_DEFAULTPORT_STATUS=fail + TLS_CONNECTOR_EXISTS=${FLAG_N} + + # Check for connector with TLS , if found ignore migrating it + checkForTlsConnector "${filePath}" "${fileName}" "${connectorCount}" + if [[ "${TLS_CONNECTOR_EXISTS}" == "${FLAG_Y}" ]]; then + return + fi + addTomcatServerListeners "${filePath}" "${fileName}" "${listenerCount}" + # Migrate RT default port from connectors + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" "${RT_PORT_YAMLPATH}" "${RT_MAXTHREADS_YAMLPATH}" "${RT_EXTRACONFIG_YAMLPATH}" "${RT_SENDREASONPHRASE_YAMLPATH}" "${RT_RELAXEDPATHCHARS_YAMLPATH}" "${RT_RELAXEDQUERYCHARS_YAMLPATH}" + # Migrate to extra if RT default ports are AJP + migrateDefaultPortIfAjp "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_RT_PORT}" + # Migrate AC default port from connectors + migrateConnectorPort "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" "${AC_PORT_YAMLPATH}" "${AC_MAXTHREADS_YAMLPATH}" "${AC_EXTRACONFIG_YAMLPATH}" "${AC_SENDREASONPHRASE_YAMLPATH}" + # Migrate to extra if access default ports are AJP + migrateDefaultPortIfAjp "${filePath}" "${fileName}" "${connectorCount}" "${DEFAULT_ACCESS_PORT}" + + if [[ "${AC_DEFAULTPORT_STATUS}" == "success" && "${RT_DEFAULTPORT_STATUS}" == "success" ]]; then + # RT and AC default port found + logger "Artifactory 8081 and Access 8040 default port are found" + migrateExtraConnectors "${filePath}" "${fileName}" "${connectorCount}" "yes" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "success" && "${RT_DEFAULTPORT_STATUS}" == "fail" ]]; then + # Only AC default port found,find RT connector + logger "Found Access default 8040 port" + findRtConnector "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "fail" && "${RT_DEFAULTPORT_STATUS}" == "success" ]]; then + # Only RT default port found,find AC connector + logger "Found Artifactory default 8081 port" + findAcConnector "${filePath}" "${fileName}" "${connectorCount}" + elif [[ "${AC_DEFAULTPORT_STATUS}" == "fail" && "${RT_DEFAULTPORT_STATUS}" == "fail" ]]; then + # RT and AC default port not found, find connector + logger "Artifactory 8081 and Access 8040 default port are not found" + findRtAndAcConnector "${filePath}" "${fileName}" "${connectorCount}" + fi +} + +# get count of connectors +getXmlConnectorCount () { + local filePath="$1" + local fileName="$2" + local count=$($LIBXML2_PATH --xpath 'count(/Server/Service/Connector)' ${filePath}/${fileName}) + echo -e "${count}" +} + +# get count of listener connectors +getTomcatServerListenersCount () { + local filePath="$1" + local fileName="$2" + local count=$($LIBXML2_PATH --xpath 'count(/Server/Listener)' ${filePath}/${fileName}) + echo -e "${count}" +} + +# Migrate server.xml configuration to system.yaml +migrateXmlFile () { + local xmlFiles= + local fileName= + local filePath= + local sourceFilePath= + DEFAULT_ACCESS_PORT="8040" + DEFAULT_RT_PORT="8081" + AC_PORT_YAMLPATH="migration.xmlFiles.serverXml.access.port" + AC_MAXTHREADS_YAMLPATH="migration.xmlFiles.serverXml.access.maxThreads" + AC_SENDREASONPHRASE_YAMLPATH="migration.xmlFiles.serverXml.access.sendReasonPhrase" + AC_EXTRACONFIG_YAMLPATH="migration.xmlFiles.serverXml.access.extraConfig" + RT_PORT_YAMLPATH="migration.xmlFiles.serverXml.artifactory.port" + RT_MAXTHREADS_YAMLPATH="migration.xmlFiles.serverXml.artifactory.maxThreads" + RT_SENDREASONPHRASE_YAMLPATH='migration.xmlFiles.serverXml.artifactory.sendReasonPhrase' + RT_RELAXEDPATHCHARS_YAMLPATH='migration.xmlFiles.serverXml.artifactory.relaxedPathChars' + RT_RELAXEDQUERYCHARS_YAMLPATH='migration.xmlFiles.serverXml.artifactory.relaxedQueryChars' + RT_EXTRACONFIG_YAMLPATH="migration.xmlFiles.serverXml.artifactory.extraConfig" + ROUTER_PORT_YAMLPATH="migration.xmlFiles.serverXml.router.port" + EXTRA_CONFIG_YAMLPATH="migration.xmlFiles.serverXml.extra.config" + EXTRA_LISTENER_CONFIG_YAMLPATH="migration.xmlFiles.serverXml.extra.listener" + RT_TOMCAT_HTTPSCONNECTOR_ENABLED="artifactory.tomcat.httpsConnector.enabled" + + retrieveYamlValue "migration.xmlFiles" "xmlFiles" "Skip" + xmlFiles="${YAML_VALUE}" + if [[ -z "${xmlFiles}" ]]; then + return + fi + bannerSection "PROCESSING MIGRATION OF XML FILES" + retrieveYamlValue "migration.xmlFiles.serverXml.fileName" "fileName" "Warning" + fileName="${YAML_VALUE}" + if [[ -z "${fileName}" ]]; then + return + fi + bannerSubSection "Processing Migration of $fileName" + retrieveYamlValue "migration.xmlFiles.serverXml.filePath" "filePath" "Warning" + filePath="${YAML_VALUE}" + if [[ -z "${filePath}" ]]; then + return + fi + # prepend NEW_DATA_DIR only if filePath is relative path + sourceFilePath=$(prependDir "${filePath}" "${NEW_DATA_DIR}/${filePath}") + if [[ "$(checkFileExists "${sourceFilePath}/${fileName}")" == "true" ]]; then + logger "File [${fileName}] is found in path [${sourceFilePath}]" + local connectorCount=$(getXmlConnectorCount "${sourceFilePath}" "${fileName}") + if [[ "${connectorCount}" == "0" ]]; then + logger "No connectors found in the [${filePath}/${fileName}],skipping migration of xml configuration" + return + fi + local listenerCount=$(getTomcatServerListenersCount "${sourceFilePath}" "${fileName}") + xmlMigrateOperation "${sourceFilePath}" "${fileName}" "${connectorCount}" "${listenerCount}" + else + logger "File [${fileName}] is not found in path [${sourceFilePath}] to migrate" + fi +} + +compareArtifactoryUser () { + local property="$1" + local oldPropertyValue="$2" + local newPropertyValue="$3" + local yamlPath="$4" + local sourceFile="$5" + + if [[ "${oldPropertyValue}" != "${newPropertyValue}" ]]; then + setSystemValue "${yamlPath}" "${oldPropertyValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" + else + logger "No change in property [${property}] value in [${sourceFile}] to migrate" + fi +} + +migrateReplicator () { + local property="$1" + local oldPropertyValue="$2" + local yamlPath="$3" + + setSystemValue "${yamlPath}" "${oldPropertyValue}" "${SYSTEM_YAML_PATH}" + logger "Setting [${yamlPath}] with value of the property [${property}] in system.yaml" +} + +compareJavaOptions () { + local property="$1" + local oldPropertyValue="$2" + local newPropertyValue="$3" + local yamlPath="$4" + local sourceFile="$5" + local oldJavaOption= + local newJavaOption= + local extraJavaOption= + local check=false + local success=true + local status=true + + oldJavaOption=$(echo "${oldPropertyValue}" | awk 'BEGIN{FS=OFS="\""}{for(i=2;i.+)\.{{ include "artifactory.fullname" . }} {{ include "artifactory.fullname" . }} +{{ tpl (include "artifactory.nginx.hosts" .) . }}; + +if ($http_x_forwarded_proto = '') { + set $http_x_forwarded_proto $scheme; +} +set $host_port {{ .Values.nginx.https.externalPort }}; +if ( $scheme = "http" ) { + set $host_port {{ .Values.nginx.http.externalPort }}; +} +## Application specific logs +## access_log /var/log/nginx/artifactory-access.log timing; +## error_log /var/log/nginx/artifactory-error.log; +rewrite ^/artifactory/?$ / redirect; +if ( $repo != "" ) { + rewrite ^/(v1|v2)/(.*) /artifactory/api/docker/$repo/$1/$2 break; +} +chunked_transfer_encoding on; +client_max_body_size 0; + +location / { + proxy_read_timeout 900; + proxy_pass_header Server; + proxy_cookie_path ~*^/.* /; + proxy_pass {{ include "artifactory.scheme" . }}://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalPort }}/; + {{- if .Values.nginx.service.ssloffload}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$host_port; + proxy_set_header X-Forwarded-Port $server_port; + {{- end }} + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- if .Values.nginx.disableProxyBuffering}} + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + {{- end }} + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + location /artifactory/ { + if ( $request_uri ~ ^/artifactory/(.*)$ ) { + proxy_pass http://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/$1; + } + proxy_pass http://{{ include "artifactory.fullname" . }}:{{ .Values.artifactory.externalArtifactoryPort }}/artifactory/; + } + location /pipelines/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + {{- if .Values.router.tlsEnabled }} + proxy_pass https://{{ include "artifactory.fullname" . }}:{{ .Values.router.internalPort }}; + {{- else }} + proxy_pass http://{{ include "artifactory.fullname" . }}:{{ .Values.router.internalPort }}; + {{- end }} + } +} +} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/nginx-main-conf.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/nginx-main-conf.yaml new file mode 100644 index 0000000000..6ee7f98f9e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/nginx-main-conf.yaml @@ -0,0 +1,83 @@ +# Main Nginx configuration file +worker_processes 4; + +{{- if .Values.nginx.logs.stderr }} +error_log stderr {{ .Values.nginx.logs.level }}; +{{- else -}} +error_log {{ .Values.nginx.persistence.mountPath }}/logs/error.log {{ .Values.nginx.logs.level }}; +{{- end }} +pid /var/run/nginx.pid; + +{{- if .Values.artifactory.ssh.enabled }} +## SSH Server Configuration +stream { + server { + {{- if .Values.nginx.singleStackIPv6Cluster }} + listen [::]:{{ .Values.nginx.ssh.internalPort }}; + {{- else -}} + listen {{ .Values.nginx.ssh.internalPort }}; + {{- end }} + proxy_pass {{ include "artifactory.fullname" . }}:{{ .Values.artifactory.ssh.externalPort }}; + } +} +{{- end }} + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + variables_hash_max_size 1024; + variables_hash_bucket_size 64; + server_names_hash_max_size 4096; + server_names_hash_bucket_size 128; + types_hash_max_size 2048; + types_hash_bucket_size 64; + proxy_read_timeout 2400s; + client_header_timeout 2400s; + client_body_timeout 2400s; + proxy_connect_timeout 75s; + proxy_send_timeout 2400s; + proxy_buffer_size 128k; + proxy_buffers 40 128k; + proxy_busy_buffers_size 128k; + proxy_temp_file_write_size 250m; + proxy_http_version 1.1; + client_body_buffer_size 128k; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + log_format timing 'ip = $remote_addr ' + 'user = \"$remote_user\" ' + 'local_time = \"$time_local\" ' + 'host = $host ' + 'request = \"$request\" ' + 'status = $status ' + 'bytes = $body_bytes_sent ' + 'upstream = \"$upstream_addr\" ' + 'upstream_time = $upstream_response_time ' + 'request_time = $request_time ' + 'referer = \"$http_referer\" ' + 'UA = \"$http_user_agent\"'; + + {{- if .Values.nginx.logs.stdout }} + access_log /dev/stdout timing; + {{- else -}} + access_log {{ .Values.nginx.persistence.mountPath }}/logs/access.log timing; + {{- end }} + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; + +} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/system.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/system.yaml new file mode 100644 index 0000000000..053207fd01 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/files/system.yaml @@ -0,0 +1,156 @@ +router: + serviceRegistry: + insecure: {{ .Values.router.serviceRegistry.insecure }} +shared: +{{- if .Values.artifactory.coldStorage.enabled }} + jfrogColdStorage: + coldInstanceEnabled: true +{{- end }} +{{ tpl (include "artifactory.metrics" .) . }} + logging: + consoleLog: + enabled: {{ .Values.artifactory.consoleLog }} + extraJavaOpts: > + -Dartifactory.graceful.shutdown.max.request.duration.millis={{ mul .Values.artifactory.terminationGracePeriodSeconds 1000 }} + -Dartifactory.access.client.max.connections={{ .Values.access.tomcat.connector.maxThreads }} + {{- with .Values.artifactory.javaOpts }} + {{- if .corePoolSize }} + -Dartifactory.async.corePoolSize={{ .corePoolSize }} + {{- end }} + {{- if .xms }} + -Xms{{ .xms }} + {{- end }} + {{- if .xmx }} + -Xmx{{ .xmx }} + {{- end }} + {{- if .jmx.enabled }} + -Dcom.sun.management.jmxremote + -Dcom.sun.management.jmxremote.port={{ .jmx.port }} + -Dcom.sun.management.jmxremote.rmi.port={{ .jmx.port }} + -Dcom.sun.management.jmxremote.ssl={{ .jmx.ssl }} + {{- if .jmx.host }} + -Djava.rmi.server.hostname={{ tpl .jmx.host $ }} + {{- else }} + -Djava.rmi.server.hostname={{ template "artifactory.fullname" $ }} + {{- end }} + {{- if .jmx.authenticate }} + -Dcom.sun.management.jmxremote.authenticate=true + -Dcom.sun.management.jmxremote.access.file={{ .jmx.accessFile }} + -Dcom.sun.management.jmxremote.password.file={{ .jmx.passwordFile }} + {{- else }} + -Dcom.sun.management.jmxremote.authenticate=false + {{- end }} + {{- end }} + {{- if .other }} + {{ .other }} + {{- end }} + {{- end }} + {{- if or .Values.database.type .Values.postgresql.enabled }} + database: + allowNonPostgresql: {{ .Values.database.allowNonPostgresql }} + {{- if .Values.postgresql.enabled }} + type: postgresql + url: "jdbc:postgresql://{{ .Release.Name }}-postgresql:{{ .Values.postgresql.service.port }}/{{ .Values.postgresql.postgresqlDatabase }}" + driver: org.postgresql.Driver + username: "{{ .Values.postgresql.postgresqlUsername }}" + {{- else }} + type: "{{ .Values.database.type }}" + driver: "{{ .Values.database.driver }}" + {{- end }} + {{- end }} +artifactory: +{{- if or .Values.artifactory.haDataDir.enabled .Values.artifactory.haBackupDir.enabled }} + node: + {{- if .Values.artifactory.haDataDir.path }} + haDataDir: {{ .Values.artifactory.haDataDir.path }} + {{- end }} + {{- if .Values.artifactory.haBackupDir.path }} + haBackupDir: {{ .Values.artifactory.haBackupDir.path }} + {{- end }} +{{- end }} + database: + maxOpenConnections: {{ .Values.artifactory.database.maxOpenConnections }} + tomcat: + maintenanceConnector: + port: {{ .Values.artifactory.tomcat.maintenanceConnector.port }} + connector: + maxThreads: {{ .Values.artifactory.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.artifactory.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.artifactory.tomcat.connector.extraConfig }} +frontend: + session: + timeMinutes: {{ .Values.frontend.session.timeoutMinutes | quote }} +access: + runOnArtifactoryTomcat: {{ .Values.access.runOnArtifactoryTomcat | default false }} + database: + maxOpenConnections: {{ .Values.access.database.maxOpenConnections }} + {{- if not (.Values.access.runOnArtifactoryTomcat | default false) }} + extraJavaOpts: > + {{- if .Values.splitServicesToContainers }} + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=70 + {{- end }} + {{- with .Values.access.javaOpts }} + {{- if .other }} + {{ .other }} + {{- end }} + {{- end }} + {{- end }} + tomcat: + connector: + maxThreads: {{ .Values.access.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.access.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.access.tomcat.connector.extraConfig }} +{{- if .Values.mc.enabled }} +mc: + enabled: true + database: + maxOpenConnections: {{ .Values.mc.database.maxOpenConnections }} + idgenerator: + maxOpenConnections: {{ .Values.mc.idgenerator.maxOpenConnections }} + tomcat: + connector: + maxThreads: {{ .Values.mc.tomcat.connector.maxThreads }} + sendReasonPhrase: {{ .Values.mc.tomcat.connector.sendReasonPhrase }} + extraConfig: {{ .Values.mc.tomcat.connector.extraConfig }} +{{- end }} +metadata: + database: + maxOpenConnections: {{ .Values.metadata.database.maxOpenConnections }} +{{- if and .Values.jfconnect.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository)) }} +jfconnect: + enabled: true +{{- else }} +jfconnect: + enabled: false +jfconnect_service: + enabled: false +{{- end }} +{{- if and .Values.federation.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository)) }} +federation: + enabled: true + embedded: {{ .Values.federation.embedded }} + extraJavaOpts: {{ .Values.federation.extraJavaOpts }} + port: {{ .Values.federation.internalPort }} +rtfs: + database: + driver: org.postgresql.Driver + type: postgresql + username: {{ .Values.federation.database.username }} + password: {{ .Values.federation.database.password }} + url: jdbc:postgresql://{{ .Values.federation.database.host }}:{{ .Values.federation.database.port }}/{{ .Values.federation.database.name }} +{{- else }} +federation: + enabled: false +{{- end }} +{{- if .Values.event.webhooks }} +event: + webhooks: {{ toYaml .Values.event.webhooks | nindent 6 }} +{{- end }} +{{- if .Values.evidence.enabled }} +evidence: + enabled: true +{{- else }} +evidence: + enabled: false +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/logo/artifactory-logo.png b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/logo/artifactory-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6c23c5a7f87edaf49f883ffa99d875073e2285 GIT binary patch literal 82419 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 z zKRlv8)uyKuJ<|L{cE7kNX^5tf?>5g<$u3VtXX%#6Ye4p>-=WvfeM4aNl8nP`a-yYvuwiFyE@Q*b+AkLI}l zFnuDc1TWeOokNy+W>0Nff^_j}Ugr~Ht6qk4&CHu`*WLMr13mUoJw!P``+4TGKfXb~d z_WuGW>X-k?4XtH+n`HKURH4(;tPzuc*1~aGBM&={+U0IJ1rFUMnh@0VBF_LX|G4_K z$P-zk{Z=DbFeVNe<1GqjHzBqvprTH2!~vyNv@dU{eu2~Qp^eLeOsHXg`scYYV9Oq9 zAO~CW-Wju>2F6lO?$T#7j-?T3v*kV5bT|o)@>l&(>?ydyoNOZwio(xEi38O&(Vn{q zBly9#PZ9ocnWdpV$#rgOP{j7fWlhxIiEBEdx**HF^EKJqj{H8Wy4D4tmgi=(^HFqR z=0)1lMNuICK0VrVtX41CU5rYWS({8sJT4AdH{NIoxx?e_bJ{I0)miS1d*1Rmr$ye@ zDadHi8F`Hu(e|g&g?Np%;3Y~k1>dRAd6g7G;*B`eHUQih8(G``f^Cx8bD!3;R8_p+@5 zp+_7X47B`a>IUFGkvi!Pq)I7D_DVHIm2RcpTiB(Fcl)0G`emk*2+cN%C?=2}RzFty02em=3;T_hN7vea>VVR_4Ce`&PDe~tF(Si#08r+-3bmC%kZ1&*tc~3;=5UupXQhBLm z!o=~f$%A!yHIS+9zn$NOmJ33$CgN+U)MGHISHetIitgoJeq}C~jgiFwWnfE^6V%TG zwC#O~3ku}_V^L11MaLyi*9vH80i$V&>m*Wn@W?Id287{&AJS_9$Z69lB+ z0iwD)V{#I4aNsRoaHXM3%InD!7$581@CQ^>n?e>Iv_@HuF5OEDbvBSd)GeIk0#-Uw zJE!UN_Ro||I-V+@{eE$Z^3n%j3l6+yLVX}6b`w9^qil=+z~htqsITCME1|i*|7?3& z^(*-;iuUoxK{sxVrO=GJ)PagdryU@Zz~3t`c^JI`>RgHXMe~p#sAwhYP)Hw4neYne zu8(e}w?*lJv2Biiu`99rZaCWHI-)DTpza=!#ReUlitU3tfW|l5mAI##GzOp?L1yej!AGgoh~7 zLcM}31WnvN+b1zc5U2eFAjqPmohrzLi{spqEb0JwTqN%7T4O*NQ}{k+u1db+SA{9O zsuQa4i#V4uROcX0=fydg15R_{G|jdswQbb8hAB-{NMi`GJY*T4`bPop7hY}xV|%nW zt{82zZvn8Gt;3|FePWInP#Y^rux^!{FPq+^ecU!<%SKoy+ATmlh=(S|P_qbyNItMW5Gxm+no+A4 zh`{o15#PE(+1+h8C%Yr_m6Dbe?c&ciCgjb)IO`l=P>q*mX6iA3r;LzW*vk7I~u33(y0+HE1j z)S2LkkZ|HqbbggMOt`RFUy?Es>;hz^>IEgODepc5?P9X~z%`Sxr*eg2M2S_8KS8a5 z*!07)88oDu1@FMKj(8tEOIh!CSjb@)YWoy&+*M!3R{S9y>>Mrtz* zA;ea@reKL+R;)VYXGwi`jsGk_P9UoojQRyg5rHAz{=pqcMro+x+g(<3ysocCzI9I*@6^aK}D%aUG(t@-Hzv>QrS2? zjm^&tjv&>-49k8D?s7va7%Gc;u16iPb35N87)rSUgOq@4kt9C?$33Etmh4Cw9o`C- zE@P07;`K^L5&*pht^;se7HfkJC{YgP6t?h}%NOepBLgF{ZQ`G*(4-)n0f)%;tS|{_ z4yby8G=ZV+f*qiiHFD&~lq9s9C=>U0R_>py-1U>MkK9B>Fi26s5W5r2FH~xqwmHrnioHa+DFeh} zp&rH+DjgvCBU6m#4#gWVJQk6+cJO`8t8jY_J7biGJ0>Rj3>v!;%?h~xkh|S8_36|E zdS+leh+bCe6G)3=?+G(SzH|g=f!=kahz%@s*SQYxAonz9e|%daI_GBoZr!>={wI$f zI_FFgZ(gKMPG3O=Hu@$8+|KO&U-UOzM1@f^nk_0FK$JzP+kzucfU^bPxeD@dEH=Xp z{-KE{0W>(y8H>!^9oeuasA-QaKM|W%VQi&q^iwgT)fGj7U`@d5(Sq@3Vc^~9z$ zszEi70@kt-StV=D3)njQL{1`^nsbDe_M9CPqX^hV{DEIzK6tKXAqJZo8pk}!-5*7n&$8ZtIjuB z#PUjhMv+HUORE5Bra|7vArybX9OYS!rM#}RX#A2lWkZ_y2h&#BJo6uKPH!i^+i?`g zFfbo?2nq&S!=xy`5e9C1{YP>U=a*&q2Q~T^V3v4pQkc?+(zP488})=YopPp3oDMcf z<>pmZ%0@eqh{4j};{yNaKxB>fL-eD3Owlew8P~;Ppw2ie$wC`ac>Q0DP59?LyUxcO z3xHU3&HmKxf+xNGPynePgCsMLya4PA1jRzW9IB;6IOA{>v9O}i8Zn$$Y;*)BnO0-+ zg_rPGNN^yhrz^*`o<}dVnb8Ab`XH@)V1!AUk`%Mnb^6kcUs>Rg!$G)Wpnm%^LMnzR z;E!t5iV>7d$a_@!t+||kDJ8ra#?^lar7t%O!#2AwG5Z*1%KZrm_csVjw9nMT+WY_ryW$JU_pM1Ht_NheERD z1)D2lsagaW_``3$@rcIH6ovxDrAg+ut@@dB)$Gt52mK7HEk)tpUJA9v7XK0aH-Ij7 zSe@i<-j?Kqj_78RpLx|n&8>Dsjo!pleVA%mm_g`|>2W=$YRSVtI~@+STYQ0!v3!c0u|CMt$zmivKl8%S}U-mWuu6>vHhNuLjXT zIDm4{G$?02p=5rC0*zuP&_?tQ_#!L<+sTS}Kw(kKv$e*RqB;sY!Ls|+0m`t25$hhv*tjT<<#*vMuBh>X1QR5MJ41h?!EnJlo*^FG43_$1GbQXr7jP$#k369#Z zn;)fAok*_%<_8yz7JxZ#xrp|I&YB68fw?HdY|4TvsUgrDpS!blGt*^?j2=d$i1#hj z$OA|JLE+<4w+0U<0D}?{7n_mdQpXbk5?%r84Ei~4O!+yPDr~30v%7cEnW`ZRx{Zzoi@ z|Kz1lR7v?S9y?i?0xT0mg+>Gdo;VNVe4i+-A>%fRQ~jS*3uw(_*x-@SRS4vfk0=&# zcoBJG3wR>9O=4~nU%}%PnIK)!q`c?vdL(2q>X5+)V8ll^W|sV+Sr>-Fl-CP)c;kX3 z>wQWJO_t|PsXiPbdK1W=0%3xM@}R&tzyOD##$p->8rMWX93eH~cxt;&?t(QcyqQTZ zyNV_T?-&J1aOv%)dJTA0goZ8ydj>H7_Mk~(0wguAQc}afVr+47IuabE`h}`UfxQ9# znVGCL_0TlZ?P*Q!0MCixy;AyB{f*$#jGc#D7{X@(R{)*#sMRd+A?20O1`eX9Cx^dF zQb({0DPhR_xq~g`?is87;uXbo!k`06wj%Y%vcYe#w|6w1cxcQ-U8Cq?n5n?VR2k|_ zNeGez;YEP(dcOQ8Zi9zzVR53!1CPA0&#iLiBzI2WrM7gNkoZ!09&P$7Dsgj^}+{dygKOk zDM^X;3oAc>Yf(0R=u-CDZxJU+{QMDWS54?V3t>lq!E6AK9kPI;NauLq)p0xrhJ3OF zDIBQB9=2%Ik5p*Wq8y=hq!Vz4cETmn)PcxAdP9qHPI#pnw6$XMANmD9zz0DO137o2 z?|k$Q>aztkwr&}XP&N(k|9S`3OSFt1I@qEma3KNsoCgq2_AW-1H{W=fF^O~#_+`76 z)voHymo3~DQO##;h6F46cAyj(_nh;mpza(A?@x?R)8H6#<`OIiS8b!H)?}T{zs9K!zG$1xZjU$lJ-GLX4%v8yY&r>=GRDnpe zvah#Ne6YQ}EhV!{?hTIOEw0C00|9+~S{d1PFoP`g02V+M$!c7c0SX7lK4+<V2W^2m|K1Mj{v^ zfc_UidnUY9NM-&rvz8vMc`hup zjDF!+rWH^rOY8U@XV_Ps6m)qHMuCGiB&SuOdq6ux{-FC{1|+%4Q{)p#(yPrq$;>4M>B{14%!@LOQ>9FG~VHSa)zuJE2ELZJfIqAk1Xng6@drGzx1H zekgq$7+qT#JdvLZPbw2bICsmAvu(Qby<@}qgPa~({)&E7oDu)gPE;O(^r<99ZTJECjV<|NCNPSLP;U?Avpbpzz^TY(qh%ah zo-S660=@RoqTL|PkqpSn^(#rpiJ=VRFE%F{RU@63^+)dnTwTKAB9zDTST!N0>2wRXQ6vR=ElmR?(>P^~O51(Q@(=%!C0RUz{^X-|G;ldOn?%U=0oMs5D@AVRmdvoGFfR=oCDCZSkRTo;@v#hI|H zchw<9u{)&~h|1r=?@tfOXX1W04g&0TJviYZsjKJf9vIdWJx-7zqk6{< z%x_0;ebTb~=T&d^W+>7*+zEM#>r>%g@|F7Hlo$ene^8ajG_kyE0$fUGXO}u^-j)XW zwCb#dC5=84_Z2}sGmZsEa+P7QJlldExeCBiTFV4zzyiv&nnUhW6f~Vs9N{ctTutdn zkFs8!e(ky7Ry;O!yULi>vRDk$40Xj`O|`90^qBdnAT#U=0{r4#?oyo6-^Yit1kelA z5_*9O?_N8hR6Rk_I}Cw~Z5}Qc(W> zd@LA-P^WlmEyMHIAjVF@`UF|n|KC9J=~$DD^cf>cb>x2*h@^r0N{rHrRi!Xi{xkL* z0en$zDr3!GZ&-u#-Odoa7rQR%Q=3-){rSHwLYx0@i_jVPe{hN#!h1rLJXZSCd6xex zmn1n-`|6VYWRz99hcX;eGmh+_rC&;(tIhcyV{5d8-|{$uU0H7T99j7_++$4(N?^#y z6}N3_#-o|y?i0{GsjJ!q8Td2w0wbRgXk>Jf4z$m6?=1cT%2kvndTtvb2~-5&i<~$AuP206F)G|-s2?rBO0-NH|C;^J z7YN=pJhtn%i5imDuRJ2l`}}?^MU@cu;*02@ihv;`p*d_ zieDR-NycQ!7i|i4^p67h`_by&sE+%O0wLX4zwNJ4Rq}8e=Kih42!B6XdaZci_s&() z{5>i;&+Bf}|5eg|KlgDXo86`kz_)W8(kW z;s2jI9xDso0a2a{!^)oj@h}M;>K(I6p3uFJq($(ySdRG3-u&a~;rH?4i_|_e3jD`y zrTBXn*KdoTc3fj`|K$P}c->@>O1u(6C9ePdzN_G?*0uNl{U_>+SRsTiex{FZJNWO1 z(QJX_i=KaOO8y^5@)S7stQHQG@}~b8ER5pcME>o_-(vJnwEmruzqQIgt@U3Gi&Xi) zGxG0@{5vC*75L|(tQ8*or6vCPTK~M?zr=y*|C=+?;rW*Qw)l}Y9cCT;ck-C}(fGrc GZv9`RA2Y%L literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge-extra-config.yaml new file mode 100644 index 0000000000..7bccf330d9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge-extra-config.yaml @@ -0,0 +1,41 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=200 + -Dartifactory.async.poolMaxQueueSize=100000 + -Dartifactory.http.client.max.total.connections=150 + -Dartifactory.http.client.max.connections.per.route=150 + -Dartifactory.access.client.max.connections=200 + -Dartifactory.metadata.event.operator.threads=5 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=1048576 + -XX:MaxDirectMemorySize=1024m + tomcat: + connector: + maxThreads: 800 + extraConfig: 'acceptCount="1200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 200 + +access: + tomcat: + connector: + maxThreads: 200 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 200 + +metadata: + database: + maxOpenConnections: 200 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge.yaml new file mode 100644 index 0000000000..be477939b4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-2xlarge.yaml @@ -0,0 +1,126 @@ +############################################################## +# The 2xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 6 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "4" + memory: 20Gi + limits: + # cpu: "20" + memory: 24Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 4Gi + +router: + resources: + requests: + cpu: "1" + memory: 1Gi + limits: + # cpu: "6" + memory: 2Gi + +frontend: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 1Gi + +metadata: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 2Gi + +event: + resources: + requests: + cpu: 200m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +observability: + resources: + requests: + cpu: 200m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +jfconnect: + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + # cpu: "1" + memory: 250Mi + +nginx: + replicaCount: 3 + disableProxyBuffering: true + resources: + requests: + cpu: "4" + memory: "6Gi" + limits: + # cpu: "14" + memory: "8Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "5000" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 256Gi + cpu: "64" + limits: + memory: 256Gi + # cpu: "128" \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large-extra-config.yaml new file mode 100644 index 0000000000..d97a85c9fd --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large-extra-config.yaml @@ -0,0 +1,41 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=65 + -Dartifactory.async.corePoolSize=80 + -Dartifactory.async.poolMaxQueueSize=20000 + -Dartifactory.http.client.max.total.connections=100 + -Dartifactory.http.client.max.connections.per.route=100 + -Dartifactory.access.client.max.connections=125 + -Dartifactory.metadata.event.operator.threads=4 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=524288 + -XX:MaxDirectMemorySize=512m + tomcat: + connector: + maxThreads: 500 + extraConfig: 'acceptCount="800" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 100 + +access: + tomcat: + connector: + maxThreads: 125 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 100 + +metadata: + database: + maxOpenConnections: 100 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large.yaml new file mode 100644 index 0000000000..80326a8e43 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-large.yaml @@ -0,0 +1,126 @@ +############################################################## +# The large sizing +# This size is intended for large organizations. It can be increased with adding replicas or moving to the xlarge sizing +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 3 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "2" + memory: 10Gi + limits: + # cpu: "14" + memory: 12Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "8" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 1 + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 200m + memory: 400Mi + limits: + # cpu: "4" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + # cpu: "4" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "1" + memory: "500Mi" + limits: + # cpu: "4" + memory: "1Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "600" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 64Gi + cpu: "16" + limits: + memory: 64Gi + # cpu: "32" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium-extra-config.yaml new file mode 100644 index 0000000000..1c294c0439 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium-extra-config.yaml @@ -0,0 +1,41 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=40 + -Dartifactory.async.poolMaxQueueSize=10000 + -Dartifactory.http.client.max.total.connections=50 + -Dartifactory.http.client.max.connections.per.route=50 + -Dartifactory.access.client.max.connections=75 + -Dartifactory.metadata.event.operator.threads=3 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=262144 + -XX:MaxDirectMemorySize=256m + tomcat: + connector: + maxThreads: 300 + extraConfig: 'acceptCount="600" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 50 + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + +metadata: + database: + maxOpenConnections: 50 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium.yaml new file mode 100644 index 0000000000..8b72150419 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-medium.yaml @@ -0,0 +1,126 @@ +############################################################## +# The medium sizing +# This size is just 2 replicas of the small size. Vertical sizing of all services is not changed +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 2 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + # cpu: "10" + memory: 5Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + # cpu: "1" + memory: 500Mi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "100m" + memory: "100Mi" + limits: + # cpu: "2" + memory: "500Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "200" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 32Gi + cpu: "8" + limits: + memory: 32Gi + # cpu: "16" \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small-extra-config.yaml new file mode 100644 index 0000000000..1c294c0439 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small-extra-config.yaml @@ -0,0 +1,41 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=40 + -Dartifactory.async.poolMaxQueueSize=10000 + -Dartifactory.http.client.max.total.connections=50 + -Dartifactory.http.client.max.connections.per.route=50 + -Dartifactory.access.client.max.connections=75 + -Dartifactory.metadata.event.operator.threads=3 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=262144 + -XX:MaxDirectMemorySize=256m + tomcat: + connector: + maxThreads: 300 + extraConfig: 'acceptCount="600" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 50 + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + +metadata: + database: + maxOpenConnections: 50 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small.yaml new file mode 100644 index 0000000000..eb8d7239d8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-small.yaml @@ -0,0 +1,124 @@ +############################################################## +# The small sizing +# This is the size recommended for running Artifactory for small teams +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 1 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 4Gi + limits: + # cpu: "10" + memory: 5Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + # cpu: "1" + memory: 500Mi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 1 + disableProxyBuffering: true + resources: + requests: + cpu: "100m" + memory: "100Mi" + limits: + # cpu: "2" + memory: "500Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "100" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 16Gi + cpu: "4" + limits: + memory: 16Gi + # cpu: "10" \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge-extra-config.yaml new file mode 100644 index 0000000000..00e6099f20 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge-extra-config.yaml @@ -0,0 +1,41 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=65 + -Dartifactory.async.corePoolSize=160 + -Dartifactory.async.poolMaxQueueSize=50000 + -Dartifactory.http.client.max.total.connections=150 + -Dartifactory.http.client.max.connections.per.route=150 + -Dartifactory.access.client.max.connections=150 + -Dartifactory.metadata.event.operator.threads=5 + -XX:MaxMetaspaceSize=512m + -Djdk.nio.maxCachedBufferSize=1048576 + -XX:MaxDirectMemorySize=1024m + tomcat: + connector: + maxThreads: 600 + extraConfig: 'acceptCount="1200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 150 + +access: + tomcat: + connector: + maxThreads: 150 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 150 + +metadata: + database: + maxOpenConnections: 150 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge.yaml new file mode 100644 index 0000000000..e77152ee1e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xlarge.yaml @@ -0,0 +1,126 @@ +############################################################## +# The xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 4 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "2" + memory: 14Gi + limits: + # cpu: "14" + memory: 16Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name : JF_SHARED_NODE_HAENABLED + value: "true" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + # cpu: 1 + memory: 3Gi + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "4" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + resources: + requests: + cpu: 200m + memory: 200Mi + limits: + # cpu: "4" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 2 + disableProxyBuffering: true + resources: + requests: + cpu: "4" + memory: "4Gi" + limits: + # cpu: "12" + memory: "8Gi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "2000" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 128Gi + cpu: "32" + limits: + memory: 128Gi + # cpu: "64" \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall-extra-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall-extra-config.yaml new file mode 100644 index 0000000000..39709b691c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall-extra-config.yaml @@ -0,0 +1,42 @@ +#################################################################################### +# [WARNING] The configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +artifactory: + javaOpts: + other: > + -XX:InitialRAMPercentage=40 + -XX:MaxRAMPercentage=70 + -Dartifactory.async.corePoolSize=10 + -Dartifactory.async.poolMaxQueueSize=2000 + -Dartifactory.http.client.max.total.connections=20 + -Dartifactory.http.client.max.connections.per.route=20 + -Dartifactory.access.client.max.connections=15 + -Dartifactory.metadata.event.operator.threads=2 + -XX:MaxMetaspaceSize=400m + -XX:CompressedClassSpaceSize=96m + -Djdk.nio.maxCachedBufferSize=131072 + -XX:MaxDirectMemorySize=128m + tomcat: + connector: + maxThreads: 50 + extraConfig: 'acceptCount="200" acceptorThreadCount="2" compression="off" connectionLinger="-1" connectionTimeout="120000" enableLookups="false"' + + database: + maxOpenConnections: 15 + +access: + tomcat: + connector: + maxThreads: 15 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 15 + +metadata: + database: + maxOpenConnections: 15 + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall.yaml new file mode 100644 index 0000000000..246f830a01 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/sizing/artifactory-xsmall.yaml @@ -0,0 +1,125 @@ +############################################################## +# The xsmall sizing +# This is the minimum size recommended for running Artifactory +############################################################## +splitServicesToContainers: true +artifactory: + # Enterprise and above licenses are required for setting replicaCount greater than 1. + # Count should be equal or above the total number of licenses available for artifactory. + replicaCount: 1 + + # Require multiple Artifactory pods to run on separate nodes + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "1" + memory: 3Gi + limits: + # cpu: "10" + memory: 4Gi + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +frontend: + resources: + requests: + cpu: 50m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + resources: + requests: + cpu: 50m + memory: 100Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +observability: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +jfconnect: + resources: + requests: + cpu: 50m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +nginx: + replicaCount: 1 + disableProxyBuffering: true + resources: + requests: + cpu: "50m" + memory: "50Mi" + limits: + # cpu: "1" + memory: "250Mi" + +postgresql: + postgresqlExtendedConf: + maxConnections: "50" + primary: + affinity: + # Require PostgreSQL pod to run on a different node than Artifactory pods + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - artifactory + topologyKey: kubernetes.io/hostname + resources: + requests: + memory: 8Gi + cpu: "2" + limits: + memory: 8Gi + # cpu: "8" + diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/NOTES.txt new file mode 100644 index 0000000000..76652ac98d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/NOTES.txt @@ -0,0 +1,106 @@ +Congratulations. You have just deployed JFrog Artifactory! +{{- if .Values.artifactory.masterKey }} +{{- if and (not .Values.artifactory.masterKeySecretName) (eq .Values.artifactory.masterKey "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") }} + + +***************************************** WARNING ****************************************** +* Your Artifactory master key is still set to the provided example: * +* artifactory.masterKey=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF * +* * +* You should change this to your own generated key: * +* $ export MASTER_KEY=$(openssl rand -hex 32) * +* $ echo ${MASTER_KEY} * +* * +* Pass the created master key to helm with '--set artifactory.masterKey=${MASTER_KEY}' * +* * +* Alternatively, you can use a pre-existing secret with a key called master-key with * +* '--set artifactory.masterKeySecretName=${SECRET_NAME}' * +******************************************************************************************** +{{- end }} +{{- end }} + +{{- if .Values.artifactory.joinKey }} +{{- if eq .Values.artifactory.joinKey "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE" }} + + +***************************************** WARNING ****************************************** +* Your Artifactory join key is still set to the provided example: * +* artifactory.joinKey=EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE * +* * +* You should change this to your own generated key: * +* $ export JOIN_KEY=$(openssl rand -hex 32) * +* $ echo ${JOIN_KEY} * +* * +* Pass the created master key to helm with '--set artifactory.joinKey=${JOIN_KEY}' * +* * +******************************************************************************************** +{{- end }} +{{- end }} + +{{- if .Values.artifactory.setSecurityContext }} +****************************************** WARNING ********************************************** +* From chart version 107.84.x, `setSecurityContext` has been renamed to `podSecurityContext`, * + please change your values.yaml before upgrade , For more Info , refer to 107.84.x changelog * +************************************************************************************************* +{{- end }} + +{{- if and (or (or (or (or (or ( or ( or ( or (or (or ( or (or .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName) .Values.systemYamlOverride.existingSecret) (or .Values.artifactory.customCertificates.enabled .Values.global.customCertificates.enabled)) .Values.aws.licenseConfigSecretName) .Values.artifactory.persistence.customBinarystoreXmlSecret) .Values.access.customCertificatesSecretName) .Values.systemYamlOverride.existingSecret) .Values.artifactory.license.secret) .Values.artifactory.userPluginSecrets) (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey)) (and .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName)) (or .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName)) .Values.artifactory.unifiedSecretInstallation }} +****************************************** WARNING ************************************************************************************************** +* The unifiedSecretInstallation flag is currently enabled, which creates the unified secret. The existing secrets will continue as separate secrets.* +* Update the values.yaml with the existing secrets to add them to the unified secret. * +***************************************************************************************************************************************************** +{{- end }} + +1. Get the Artifactory URL by running these commands: + + {{- if .Values.ingress.enabled }} + {{- range .Values.ingress.hosts }} + http://{{ . }} + {{- end }} + + {{- else if contains "NodePort" .Values.nginx.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "artifactory.nginx.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT/ + + {{- else if contains "LoadBalancer" .Values.nginx.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of the service by running 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "artifactory.nginx.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "artifactory.nginx.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP/ + + {{- else if contains "ClusterIP" .Values.nginx.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "component={{ .Values.nginx.name }}" -o jsonpath="{.items[0].metadata.name}") + echo http://127.0.0.1:{{ .Values.nginx.externalPortHttp }} + kubectl port-forward --namespace {{ .Release.Namespace }} $POD_NAME {{ .Values.nginx.externalPortHttp }}:{{ .Values.nginx.internalPortHttp }} + + {{- end }} + +2. Open Artifactory in your browser + Default credential for Artifactory: + user: admin + password: password + +{{ if .Values.artifactory.javaOpts.jmx.enabled }} +JMX configuration: +{{- if not (contains "LoadBalancer" .Values.artifactory.service.type) }} +If you want to access JMX from you computer with jconsole, you should set ".Values.artifactory.service.type=LoadBalancer" !!! +{{ end }} + +1. Get the Artifactory service IP: +export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "artifactory.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + +2. Map the service name to the service IP in /etc/hosts: +sudo sh -c "echo \"${SERVICE_IP} {{ template "artifactory.fullname" . }}\" >> /etc/hosts" + +3. Launch jconsole: +jconsole {{ template "artifactory.fullname" . }}:{{ .Values.artifactory.javaOpts.jmx.port }} +{{- end }} + +{{- if and .Values.nginx.enabled .Values.ingress.hosts }} +***************************************** WARNING ***************************************************************************** +* when nginx is enabled , .Values.ingress.hosts will be deprecated in upcoming releases * +* It is recommended to use nginx.hosts instead ingress.hosts +******************************************************************************************************************************* +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_helpers.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_helpers.tpl new file mode 100644 index 0000000000..7cea041f7e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_helpers.tpl @@ -0,0 +1,528 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "artifactory.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Expand the name nginx service. +*/}} +{{- define "artifactory.nginx.name" -}} +{{- default .Chart.Name .Values.nginx.name .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). +*/}} +{{- define "artifactory.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{/* +Create a default fully qualified nginx name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "artifactory.nginx.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.nginx.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "artifactory.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{ default (include "artifactory.fullname" .) .Values.serviceAccount.name }} +{{- else -}} +{{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "artifactory.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Generate SSL certificates +*/}} +{{- define "artifactory.gen-certs" -}} +{{- $altNames := list ( printf "%s.%s" (include "artifactory.fullname" .) .Release.Namespace ) ( printf "%s.%s.svc" (include "artifactory.fullname" .) .Release.Namespace ) -}} +{{- $ca := genCA "artifactory-ca" 365 -}} +{{- $cert := genSignedCert ( include "artifactory.fullname" . ) nil $altNames 365 $ca -}} +tls.crt: {{ $cert.Cert | b64enc }} +tls.key: {{ $cert.Key | b64enc }} +{{- end -}} + +{{/* +Scheme (http/https) based on Access or Router TLS enabled/disabled +*/}} +{{- define "artifactory.scheme" -}} +{{- if or .Values.access.accessConfig.security.tls .Values.router.tlsEnabled -}} +{{- printf "%s" "https" -}} +{{- else -}} +{{- printf "%s" "http" -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve joinKey value +*/}} +{{- define "artifactory.joinKey" -}} +{{- if .Values.global.joinKey -}} +{{- .Values.global.joinKey -}} +{{- else if .Values.artifactory.joinKey -}} +{{- .Values.artifactory.joinKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve jfConnectToken value +*/}} +{{- define "artifactory.jfConnectToken" -}} +{{- .Values.artifactory.jfConnectToken -}} +{{- end -}} + +{{/* +Resolve masterKey value +*/}} +{{- define "artifactory.masterKey" -}} +{{- if .Values.global.masterKey -}} +{{- .Values.global.masterKey -}} +{{- else if .Values.artifactory.masterKey -}} +{{- .Values.artifactory.masterKey -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve joinKeySecretName value +*/}} +{{- define "artifactory.joinKeySecretName" -}} +{{- if .Values.global.joinKeySecretName -}} +{{- .Values.global.joinKeySecretName -}} +{{- else if .Values.artifactory.joinKeySecretName -}} +{{- .Values.artifactory.joinKeySecretName -}} +{{- else -}} +{{ include "artifactory.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve jfConnectTokenSecretName value +*/}} +{{- define "artifactory.jfConnectTokenSecretName" -}} +{{- if .Values.artifactory.jfConnectTokenSecretName -}} +{{- .Values.artifactory.jfConnectTokenSecretName -}} +{{- else -}} +{{ include "artifactory.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve masterKeySecretName value +*/}} +{{- define "artifactory.masterKeySecretName" -}} +{{- if .Values.global.masterKeySecretName -}} +{{- .Values.global.masterKeySecretName -}} +{{- else if .Values.artifactory.masterKeySecretName -}} +{{- .Values.artifactory.masterKeySecretName -}} +{{- else -}} +{{ include "artifactory.fullname" . }} +{{- end -}} +{{- end -}} + +{{/* +Resolve imagePullSecrets value +*/}} +{{- define "artifactory.imagePullSecrets" -}} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if .Values.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainersBegin value +*/}} +{{- define "artifactory.customInitContainersBegin" -}} +{{- if .Values.global.customInitContainersBegin -}} +{{- .Values.global.customInitContainersBegin -}} +{{- end -}} +{{- if .Values.artifactory.customInitContainersBegin -}} +{{- .Values.artifactory.customInitContainersBegin -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainers value +*/}} +{{- define "artifactory.customInitContainers" -}} +{{- if .Values.global.customInitContainers -}} +{{- .Values.global.customInitContainers -}} +{{- end -}} +{{- if .Values.artifactory.customInitContainers -}} +{{- .Values.artifactory.customInitContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumes value +*/}} +{{- define "artifactory.customVolumes" -}} +{{- if .Values.global.customVolumes -}} +{{- .Values.global.customVolumes -}} +{{- end -}} +{{- if .Values.artifactory.customVolumes -}} +{{- .Values.artifactory.customVolumes -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumeMounts value +*/}} +{{- define "artifactory.customVolumeMounts" -}} +{{- if .Values.global.customVolumeMounts -}} +{{- .Values.global.customVolumeMounts -}} +{{- end -}} +{{- if .Values.artifactory.customVolumeMounts -}} +{{- .Values.artifactory.customVolumeMounts -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customSidecarContainers value +*/}} +{{- define "artifactory.customSidecarContainers" -}} +{{- if .Values.global.customSidecarContainers -}} +{{- .Values.global.customSidecarContainers -}} +{{- end -}} +{{- if .Values.artifactory.customSidecarContainers -}} +{{- .Values.artifactory.customSidecarContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory chart image names +*/}} +{{- define "artifactory.getImageInfoByValue" -}} +{{- $dot := index . 0 }} +{{- $indexReference := index . 1 }} +{{- $registryName := index $dot.Values $indexReference "image" "registry" -}} +{{- $repositoryName := index $dot.Values $indexReference "image" "repository" -}} +{{- $tag := default $dot.Chart.AppVersion (index $dot.Values $indexReference "image" "tag") | toString -}} +{{- if $dot.Values.global }} + {{- if and $dot.Values.splitServicesToContainers $dot.Values.global.versions.router (eq $indexReference "router") }} + {{- $tag = $dot.Values.global.versions.router | toString -}} + {{- end -}} + {{- if and $dot.Values.global.versions.initContainers (eq $indexReference "initContainers") }} + {{- $tag = $dot.Values.global.versions.initContainers | toString -}} + {{- end -}} + {{- if and $dot.Values.global.versions.artifactory (or (eq $indexReference "artifactory") (eq $indexReference "nginx") ) }} + {{- $tag = $dot.Values.global.versions.artifactory | toString -}} + {{- end -}} + {{- if $dot.Values.global.imageRegistry }} + {{- printf "%s/%s:%s" $dot.Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper artifactory app version +*/}} +{{- define "artifactory.app.version" -}} +{{- $tag := (splitList ":" ((include "artifactory.getImageInfoByValue" (list . "artifactory" )))) | last | toString -}} +{{- printf "%s" $tag -}} +{{- end -}} + +{{/* +Custom certificate copy command +*/}} +{{- define "artifactory.copyCustomCerts" -}} +echo "Copy custom certificates to {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted"; +mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted; +for file in $(ls -1 /tmp/certs/* | grep -v .key | grep -v ":" | grep -v grep); do if [ -f "${file}" ]; then cp -v ${file} {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted; fi done; +if [ -f {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/tls.crt ]; then mv -v {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/tls.crt {{ .Values.artifactory.persistence.mountPath }}/etc/security/keys/trusted/ca.crt; fi; +{{- end -}} + +{{/* +Circle of trust certificates copy command +*/}} +{{- define "artifactory.copyCircleOfTrustCertsCerts" -}} +echo "Copy circle of trust certificates to {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted"; +mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; +for file in $(ls -1 /tmp/circleoftrustcerts/* | grep -v .key | grep -v ":" | grep -v grep); do if [ -f "${file}" ]; then cp -v ${file} {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; fi done; +{{- end -}} + +{{/* +Resolve requiredServiceTypes value +*/}} +{{- define "artifactory.router.requiredServiceTypes" -}} +{{- $requiredTypes := "jfrt,jfac" -}} +{{- if not .Values.access.enabled -}} + {{- $requiredTypes = "jfrt" -}} +{{- end -}} +{{- if .Values.observability.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfob" -}} +{{- end -}} +{{- if .Values.metadata.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfmd" -}} +{{- end -}} +{{- if .Values.event.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfevt" -}} +{{- end -}} +{{- if .Values.frontend.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jffe" -}} +{{- end -}} +{{- if .Values.jfconnect.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfcon" -}} +{{- end -}} +{{- if .Values.evidence.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfevd" -}} +{{- end -}} +{{- if .Values.mc.enabled -}} + {{- $requiredTypes = printf "%s,%s" $requiredTypes "jfmc" -}} +{{- end -}} +{{- $requiredTypes -}} +{{- end -}} + +{{/* +Check if the image is artifactory pro or not +*/}} +{{- define "artifactory.isImageProType" -}} +{{- if not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository) -}} +{{ true }} +{{- else -}} +{{ false }} +{{- end -}} +{{- end -}} + +{{/* +Check if the artifactory is using derby database +*/}} +{{- define "artifactory.isUsingDerby" -}} +{{- if and (eq (default "derby" .Values.database.type) "derby") (not .Values.postgresql.enabled) -}} +{{ true }} +{{- else -}} +{{ false }} +{{- end -}} +{{- end -}} + +{{/* +nginx scheme (http/https) +*/}} +{{- define "nginx.scheme" -}} +{{- if .Values.nginx.http.enabled -}} +{{- printf "%s" "http" -}} +{{- else -}} +{{- printf "%s" "https" -}} +{{- end -}} +{{- end -}} + +{{/* +nginx command +*/}} +{{- define "nginx.command" -}} +{{- if .Values.nginx.customCommand }} +{{ toYaml .Values.nginx.customCommand }} +{{- end }} +{{- end -}} + +{{/* +nginx port (8080/8443) based on http/https enabled +*/}} +{{- define "nginx.port" -}} +{{- if .Values.nginx.http.enabled -}} +{{- .Values.nginx.http.internalPort -}} +{{- else -}} +{{- .Values.nginx.https.internalPort -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customInitContainers value +*/}} +{{- define "artifactory.nginx.customInitContainers" -}} +{{- if .Values.nginx.customInitContainers -}} +{{- .Values.nginx.customInitContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve customVolumes value +*/}} +{{- define "artifactory.nginx.customVolumes" -}} +{{- if .Values.nginx.customVolumes -}} +{{- .Values.nginx.customVolumes -}} +{{- end -}} +{{- end -}} + + +{{/* +Resolve customVolumeMounts nginx value +*/}} +{{- define "artifactory.nginx.customVolumeMounts" -}} +{{- if .Values.nginx.customVolumeMounts -}} +{{- .Values.nginx.customVolumeMounts -}} +{{- end -}} +{{- end -}} + + +{{/* +Resolve customSidecarContainers value +*/}} +{{- define "artifactory.nginx.customSidecarContainers" -}} +{{- if .Values.nginx.customSidecarContainers -}} +{{- .Values.nginx.customSidecarContainers -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve Artifactory pod node selector value +*/}} +{{- define "artifactory.nodeSelector" -}} +nodeSelector: +{{- if .Values.global.nodeSelector }} +{{ toYaml .Values.global.nodeSelector | indent 2 }} +{{- else if .Values.artifactory.nodeSelector }} +{{ toYaml .Values.artifactory.nodeSelector | indent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Resolve Nginx pods node selector value +*/}} +{{- define "nginx.nodeSelector" -}} +nodeSelector: +{{- if .Values.global.nodeSelector }} +{{ toYaml .Values.global.nodeSelector | indent 2 }} +{{- else if .Values.nginx.nodeSelector }} +{{ toYaml .Values.nginx.nodeSelector | indent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Resolve unifiedCustomSecretVolumeName value +*/}} +{{- define "artifactory.unifiedCustomSecretVolumeName" -}} +{{- printf "%s-%s" (include "artifactory.name" .) ("unified-secret-volume") | trunc 63 -}} +{{- end -}} + +{{/* +Check the Duplication of volume names for secrets. If unifiedSecretInstallation is enabled then the method is checking for volume names, +if the volume exists in customVolume then an extra volume with the same name will not be getting added in unifiedSecretInstallation case. +*/}} +{{- define "artifactory.checkDuplicateUnifiedCustomVolume" -}} +{{- if or .Values.global.customVolumes .Values.artifactory.customVolumes -}} +{{- $val := (tpl (include "artifactory.customVolumes" .) .) | toJson -}} +{{- contains (include "artifactory.unifiedCustomSecretVolumeName" .) $val | toString -}} +{{- else -}} +{{- printf "%s" "false" -}} +{{- end -}} +{{- end -}} + +{{/* +Calculate the systemYaml from structured and unstructured text input +*/}} +{{- define "artifactory.finalSystemYaml" -}} +{{ tpl (mergeOverwrite (include "artifactory.systemYaml" . | fromYaml) .Values.artifactory.extraSystemYaml | toYaml) . }} +{{- end -}} + +{{/* +Calculate the systemYaml from the unstructured text input +*/}} +{{- define "artifactory.systemYaml" -}} +{{ include (print $.Template.BasePath "/_system-yaml-render.tpl") . }} +{{- end -}} + +{{/* +Metrics enabled +*/}} +{{- define "metrics.enabled" -}} +shared: + metrics: + enabled: true +{{- end }} + +{{/* +Resolve unified secret prepend release name +*/}} +{{- define "artifactory.unifiedSecretPrependReleaseName" -}} +{{- if .Values.artifactory.unifiedSecretPrependReleaseName }} +{{- printf "%s" (include "artifactory.fullname" .) -}} +{{- else }} +{{- printf "%s" (include "artifactory.name" .) -}} +{{- end }} +{{- end }} + +{{/* +Resolve artifactory metrics +*/}} +{{- define "artifactory.metrics" -}} +{{- if .Values.artifactory.openMetrics -}} +{{- if .Values.artifactory.openMetrics.enabled -}} +{{ include "metrics.enabled" . }} +{{- if .Values.artifactory.openMetrics.filebeat }} +{{- if .Values.artifactory.openMetrics.filebeat.enabled }} +{{ include "metrics.enabled" . }} + filebeat: +{{ tpl (.Values.artifactory.openMetrics.filebeat | toYaml) . | indent 6 }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- else if .Values.artifactory.metrics -}} +{{- if .Values.artifactory.metrics.enabled -}} +{{ include "metrics.enabled" . }} +{{- if .Values.artifactory.metrics.filebeat }} +{{- if .Values.artifactory.metrics.filebeat.enabled }} +{{ include "metrics.enabled" . }} + filebeat: +{{ tpl (.Values.artifactory.metrics.filebeat | toYaml) . | indent 6 }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve nginx hosts value +*/}} +{{- define "artifactory.nginx.hosts" -}} +{{- if .Values.ingress.hosts }} +{{- range .Values.ingress.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} +{{- end -}} +{{- else if .Values.nginx.hosts }} +{{- range .Values.nginx.hosts -}} + {{- if contains "." . -}} + {{ "" | indent 0 }} ~(?.+)\.{{ . }} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_system-yaml-render.tpl b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_system-yaml-render.tpl new file mode 100644 index 0000000000..deaa773ea9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/_system-yaml-render.tpl @@ -0,0 +1,5 @@ +{{- if .Values.artifactory.systemYaml -}} +{{- tpl .Values.artifactory.systemYaml . -}} +{{- else -}} +{{ (tpl ( $.Files.Get "files/system.yaml" ) .) }} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/additional-resources.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/additional-resources.yaml new file mode 100644 index 0000000000..c4d06f08ad --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/additional-resources.yaml @@ -0,0 +1,3 @@ +{{ if .Values.additionalResources }} +{{ tpl .Values.additionalResources . }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/admin-bootstrap-creds.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/admin-bootstrap-creds.yaml new file mode 100644 index 0000000000..eb2d613c6c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/admin-bootstrap-creds.yaml @@ -0,0 +1,15 @@ +{{- if not (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} +{{- if and .Values.artifactory.admin.password (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-bootstrap-creds + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + bootstrap.creds: {{ (printf "%s@%s=%s" .Values.artifactory.admin.username .Values.artifactory.admin.ip .Values.artifactory.admin.password) | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-access-config.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-access-config.yaml new file mode 100644 index 0000000000..4fcf85d94c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-access-config.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.access.accessConfig (not .Values.artifactory.unifiedSecretInstallation) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory.fullname" . }}-access-config + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +type: Opaque +stringData: + access.config.patch.yml: | +{{ tpl (toYaml .Values.access.accessConfig) . | indent 4 }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-binarystore-secret.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-binarystore-secret.yaml new file mode 100644 index 0000000000..6b721dd4c7 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-binarystore-secret.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.artifactory.persistence.customBinarystoreXmlSecret) (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-binarystore + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +stringData: + binarystore.xml: |- +{{- if .Values.artifactory.persistence.binarystoreXml }} +{{ tpl .Values.artifactory.persistence.binarystoreXml . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/binarystore.xml" ) . | indent 4 }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-configmaps.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-configmaps.yaml new file mode 100644 index 0000000000..359fa07d25 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-configmaps.yaml @@ -0,0 +1,13 @@ +{{ if .Values.artifactory.configMaps }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory.fullname" . }}-configmaps + labels: + app: {{ template "artifactory.fullname" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: +{{ tpl .Values.artifactory.configMaps . | indent 2 }} +{{ end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-custom-secrets.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-custom-secrets.yaml new file mode 100644 index 0000000000..4b73e79fc3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-custom-secrets.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.artifactory.customSecrets (not .Values.artifactory.unifiedSecretInstallation) }} +{{- range .Values.artifactory.customSecrets }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory.fullname" $ }}-{{ .name }} + labels: + app: "{{ template "artifactory.name" $ }}" + chart: "{{ template "artifactory.chart" $ }}" + component: "{{ $.Values.artifactory.name }}" + heritage: {{ $.Release.Service | quote }} + release: {{ $.Release.Name | quote }} +type: Opaque +stringData: + {{ .key }}: | +{{ .data | indent 4 -}} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-database-secrets.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-database-secrets.yaml new file mode 100644 index 0000000000..f98d422e95 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-database-secrets.yaml @@ -0,0 +1,24 @@ +{{- if and (not .Values.database.secrets) (not .Values.postgresql.enabled) (not .Values.artifactory.unifiedSecretInstallation) }} +{{- if or .Values.database.url .Values.database.user .Values.database.password }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory.fullname" . }}-database-creds + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +type: Opaque +data: + {{- with .Values.database.url }} + db-url: {{ tpl . $ | b64enc | quote }} + {{- end }} + {{- with .Values.database.user }} + db-user: {{ tpl . $ | b64enc | quote }} + {{- end }} + {{- with .Values.database.password }} + db-password: {{ tpl . $ | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml new file mode 100644 index 0000000000..72dee6bb84 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml @@ -0,0 +1,16 @@ +{{- if not .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} +{{- if and (.Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled) (not .Values.artifactory.unifiedSecretInstallation) }} +kind: Secret +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-gcpcreds + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +stringData: + gcp.credentials.json: |- +{{ tpl .Values.artifactory.persistence.googleStorage.gcpServiceAccount.config . | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-hpa.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-hpa.yaml new file mode 100644 index 0000000000..01f8a9fb70 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} + {{- if semverCompare ">=v1.23.0-0" .Capabilities.KubeVersion.Version }} +apiVersion: autoscaling/v2 + {{- else }} +apiVersion: autoscaling/v2beta2 + {{- end }} +kind: HorizontalPodAutoscaler +metadata: + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "artifactory.fullname" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: {{ template "artifactory.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-installer-info.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-installer-info.yaml new file mode 100644 index 0000000000..cfb95b67d3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-installer-info.yaml @@ -0,0 +1,16 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-installer-info + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + installer-info.json: | +{{- if .Values.installerInfo -}} +{{- tpl .Values.installerInfo . | nindent 4 -}} +{{- else -}} +{{ (tpl ( .Files.Get "files/installer-info.json" | nindent 4 ) .) }} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-license-secret.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-license-secret.yaml new file mode 100644 index 0000000000..ba83aaf24d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-license-secret.yaml @@ -0,0 +1,16 @@ +{{ if and (not .Values.artifactory.unifiedSecretInstallation) (not .Values.artifactory.license.secret) (not .Values.artifactory.license.licenseKey) }} +{{- with .Values.artifactory.license.licenseKey }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "artifactory.fullname" $ }}-license + labels: + app: {{ template "artifactory.name" $ }} + chart: {{ template "artifactory.chart" $ }} + heritage: {{ $.Release.Service }} + release: {{ $.Release.Name }} +type: Opaque +data: + artifactory.lic: {{ . | b64enc | quote }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-migration-scripts.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-migration-scripts.yaml new file mode 100644 index 0000000000..4b1ba40276 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-migration-scripts.yaml @@ -0,0 +1,18 @@ +{{- if .Values.artifactory.migration.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory.fullname" . }}-migration-scripts + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + migrate.sh: | +{{ .Files.Get "files/migrate.sh" | indent 4 }} + migrationHelmInfo.yaml: | +{{ .Files.Get "files/migrationHelmInfo.yaml" | indent 4 }} + migrationStatus.sh: | +{{ .Files.Get "files/migrationStatus.sh" | indent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-networkpolicy.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-networkpolicy.yaml new file mode 100644 index 0000000000..d24203dc99 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-networkpolicy.yaml @@ -0,0 +1,34 @@ +{{- range .Values.networkpolicy }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "artifactory.fullname" $ }}-{{ .name }}-networkpolicy + labels: + app: {{ template "artifactory.name" $ }} + chart: {{ template "artifactory.chart" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} +spec: +{{- if .podSelector }} + podSelector: +{{ .podSelector | toYaml | trimSuffix "\n" | indent 4 -}} +{{ else }} + podSelector: {} +{{- end }} + policyTypes: + {{- if .ingress }} + - Ingress + {{- end }} + {{- if .egress }} + - Egress + {{- end }} +{{- if .ingress }} + ingress: +{{ .ingress | toYaml | trimSuffix "\n" | indent 2 -}} +{{- end }} +{{- if .egress }} + egress: +{{ .egress | toYaml | trimSuffix "\n" | indent 2 -}} +{{- end }} +--- +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-nfs-pvc.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-nfs-pvc.yaml new file mode 100644 index 0000000000..75d6d0c53d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-nfs-pvc.yaml @@ -0,0 +1,101 @@ +{{- if eq .Values.artifactory.persistence.type "nfs" }} +### Artifactory HA data +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "artifactory.fullname" . }}-data-pv + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + id: {{ template "artifactory.name" . }}-data-pv + type: nfs-volume +spec: + {{- if .Values.artifactory.persistence.nfs.mountOptions }} + mountOptions: +{{ toYaml .Values.artifactory.persistence.nfs.mountOptions | indent 4 }} + {{- end }} + capacity: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + nfs: + server: {{ .Values.artifactory.persistence.nfs.ip }} + path: "{{ .Values.artifactory.persistence.nfs.haDataMount }}" + readOnly: false +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-data-pvc + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + type: nfs-volume +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + selector: + matchLabels: + id: {{ template "artifactory.name" . }}-data-pv + app: {{ template "artifactory.name" . }} + release: {{ .Release.Name }} +--- +### Artifactory HA backup +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "artifactory.fullname" . }}-backup-pv + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + id: {{ template "artifactory.name" . }}-backup-pv + type: nfs-volume +spec: + {{- if .Values.artifactory.persistence.nfs.mountOptions }} + mountOptions: +{{ toYaml .Values.artifactory.persistence.nfs.mountOptions | indent 4 }} + {{- end }} + capacity: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + nfs: + server: {{ .Values.artifactory.persistence.nfs.ip }} + path: "{{ .Values.artifactory.persistence.nfs.haBackupMount }}" + readOnly: false +--- +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "artifactory.fullname" . }}-backup-pvc + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + type: nfs-volume +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: {{ .Values.artifactory.persistence.nfs.capacity }} + selector: + matchLabels: + id: {{ template "artifactory.name" . }}-backup-pv + app: {{ template "artifactory.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-pdb.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-pdb.yaml new file mode 100644 index 0000000000..68876d23b0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/artifactory-pdb.yaml @@ -0,0 +1,24 @@ +{{- if .Values.artifactory.minAvailable -}} +{{- if semverCompare "= 107.79.x), just set databaseUpgradeReady=true \n" .Values.databaseUpgradeReady | quote }} +{{- end }} +{{- with .Values.artifactory.statefulset.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +{{- if and (eq (include "artifactory.isUsingDerby" .) "true") (gt (.Values.artifactory.replicaCount | int64) 1) }} + {{- fail "Derby database is not supported in HA mode" }} +{{- end }} +{{- if .Values.artifactory.postStartCommand }} + {{- fail ".Values.artifactory.postStartCommand is not supported and should be replaced with .Values.artifactory.lifecycle.postStart.exec.command" }} +{{- end }} +{{- if eq .Values.artifactory.persistence.type "aws-s3" }} + {{- fail "\nPersistence storage type 'aws-s3' is deprecated and is not supported and should be replaced with 'aws-s3-v3'" }} +{{- end }} +{{- if or .Values.artifactory.persistence.googleStorage.identity .Values.artifactory.persistence.googleStorage.credential }} + {{- fail "\nGCP Bucket Authentication with Identity and Credential is deprecated" }} +{{- end }} +{{- if (eq (.Values.artifactory.setSecurityContext | toString) "false" ) }} + {{- fail "\n You need to set security context at the pod level. .Values.artifactory.setSecurityContext is no longer supported. Replace it with .Values.artifactory.podSecurityContext" }} +{{- end }} +{{- if or .Values.artifactory.uid .Values.artifactory.gid }} +{{- if or (not (eq (.Values.artifactory.uid | toString) "1030" )) (not (eq (.Values.artifactory.gid | toString) "1030" )) }} + {{- fail "\n .Values.artifactory.uid and .Values.artifactory.gid are no longer supported. You need to set these values at the pod security context level. Replace them with .Values.artifactory.podSecurityContext.runAsUser .Values.artifactory.podSecurityContext.runAsGroup and .Values.artifactory.podSecurityContext.fsGroup" }} +{{- end }} +{{- end }} +{{- if or .Values.artifactory.fsGroupChangePolicy .Values.artifactory.seLinuxOptions }} + {{- fail "\n .Values.artifactory.fsGroupChangePolicy and .Values.artifactory.seLinuxOptions are no longer supported. You need to set these values at the pod security context level. Replace them with .Values.artifactory.podSecurityContext.fsGroupChangePolicy and .Values.artifactory.podSecurityContext.seLinuxOptions" }} +{{- end }} +{{- if .Values.initContainerImage }} + {{- fail "\n .Values.initContainerImage is no longer supported. Replace it with .Values.initContainers.image.registry .Values.initContainers.image.repository and .Values.initContainers.image.tag" }} +{{- end }} +spec: + serviceName: {{ template "artifactory.name" . }} + replicas: {{ .Values.artifactory.replicaCount }} + updateStrategy: {{- toYaml .Values.artifactory.updateStrategy | nindent 4 }} + selector: + matchLabels: + app: {{ template "artifactory.name" . }} + role: {{ template "artifactory.name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + role: {{ template "artifactory.name" . }} + component: {{ .Values.artifactory.name }} + release: {{ .Release.Name }} + {{- with .Values.artifactory.labels }} +{{ toYaml . | indent 8 }} + {{- end }} + annotations: + {{- if not .Values.artifactory.unifiedSecretInstallation }} + checksum/database-secrets: {{ include (print $.Template.BasePath "/artifactory-database-secrets.yaml") . | sha256sum }} + checksum/binarystore: {{ include (print $.Template.BasePath "/artifactory-binarystore-secret.yaml") . | sha256sum }} + checksum/systemyaml: {{ include (print $.Template.BasePath "/artifactory-system-yaml.yaml") . | sha256sum }} + {{- if .Values.access.accessConfig }} + checksum/access-config: {{ include (print $.Template.BasePath "/artifactory-access-config.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + checksum/gcpcredentials: {{ include (print $.Template.BasePath "/artifactory-gcp-credentials-secret.yaml") . | sha256sum }} + {{- end }} + {{- if not (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} + checksum/admin-creds: {{ include (print $.Template.BasePath "/admin-bootstrap-creds.yaml") . | sha256sum }} + {{- end }} + {{- else }} + checksum/artifactory-unified-secret: {{ include (print $.Template.BasePath "/artifactory-unified-secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.artifactory.annotations }} +{{ toYaml . | indent 8 }} + {{- end }} + spec: + {{- if .Values.artifactory.schedulerName }} + schedulerName: {{ .Values.artifactory.schedulerName | quote }} + {{- end }} + {{- if .Values.artifactory.priorityClass.existingPriorityClass }} + priorityClassName: {{ .Values.artifactory.priorityClass.existingPriorityClass }} + {{- else -}} + {{- if .Values.artifactory.priorityClass.create }} + priorityClassName: {{ default (include "artifactory.fullname" .) .Values.artifactory.priorityClass.name }} + {{- end }} + {{- end }} + serviceAccountName: {{ template "artifactory.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ add .Values.artifactory.terminationGracePeriodSeconds 10 }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory.imagePullSecrets" . | indent 6 }} + {{- end }} + {{- if .Values.artifactory.podSecurityContext.enabled }} + securityContext: {{- omit .Values.artifactory.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.artifactory.topologySpreadConstraints }} + topologySpreadConstraints: +{{ tpl (toYaml .Values.artifactory.topologySpreadConstraints) . | indent 8 }} + {{- end }} + initContainers: + {{- if or .Values.artifactory.customInitContainersBegin .Values.global.customInitContainersBegin }} +{{ tpl (include "artifactory.customInitContainersBegin" .) . | indent 6 }} + {{- end }} + {{- if .Values.artifactory.persistence.enabled }} + {{- if .Values.artifactory.deleteDBPropertiesOnStartup }} + - name: "delete-db-properties" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - 'rm -fv {{ .Values.artifactory.persistence.mountPath }}/etc/db.properties' + volumeMounts: + - name: artifactory-volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + {{- end }} + {{- end }} + {{- if or (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) .Values.artifactory.admin.password }} + - name: "access-bootstrap-creds" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > + echo "Preparing {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access; + cp -Lrf /tmp/access/bootstrap.creds {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds; + chmod 600 {{ .Values.artifactory.persistence.mountPath }}/etc/access/bootstrap.creds; + volumeMounts: + - name: artifactory-volume + mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + {{- if or (not .Values.artifactory.unifiedSecretInstallation) (and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey) }} + - name: access-bootstrap-creds + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/access/bootstrap.creds" + {{- if and .Values.artifactory.admin.secret .Values.artifactory.admin.dataKey }} + subPath: {{ .Values.artifactory.admin.dataKey }} + {{- else }} + subPath: "bootstrap.creds" + {{- end }} + {{- end }} + - name: 'copy-system-configurations' + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - '/bin/bash' + - '-c' + - > + if [[ -e "{{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml" ]]; then chmod 644 {{ .Values.artifactory.persistence.mountPath }}/etc/filebeat.yaml; fi; + echo "Copy system.yaml to {{ .Values.artifactory.persistence.mountPath }}/etc"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access/keys/trusted; + {{- if .Values.systemYamlOverride.existingSecret }} + cp -fv /tmp/etc/{{ .Values.systemYamlOverride.dataKey }} {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- else }} + cp -fv /tmp/etc/system.yaml {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml; + {{- end }} + echo "Copy binarystore.xml file"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory; + cp -fv /tmp/etc/artifactory/binarystore.xml {{ .Values.artifactory.persistence.mountPath }}/etc/artifactory/binarystore.xml; + {{- if .Values.access.accessConfig }} + echo "Copy access.config.patch.yml to {{ .Values.artifactory.persistence.mountPath }}/etc/access"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/access; + cp -fv /tmp/etc/access.config.patch.yml {{ .Values.artifactory.persistence.mountPath }}/etc/access/access.config.patch.yml; + {{- end }} + {{- if .Values.access.resetAccessCAKeys }} + echo "Resetting Access CA Keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys; + touch {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/reset_ca_keys; + {{- end }} + {{- if .Values.access.customCertificatesSecretName }} + echo "Copying custom certificates to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys; + cp -fv /tmp/etc/tls.crt {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.crt; + cp -fv /tmp/etc/tls.key {{ .Values.artifactory.persistence.mountPath }}/bootstrap/etc/access/keys/ca.private.key; + {{- end }} + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + echo "Copy joinKey to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security; + echo -n ${ARTIFACTORY_JOIN_KEY} > {{ .Values.artifactory.persistence.mountPath }}/bootstrap/access/etc/security/join.key; + {{- end }} + {{- if or .Values.artifactory.jfConnectToken .Values.artifactory.jfConnectTokenSecretName }} + echo "Copy jfConnectToken to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/registration_token"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/; + echo -n ${ARTIFACTORY_JFCONNECT_TOKEN} > {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/registration_token; + {{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + echo "Copy masterKey to {{ .Values.artifactory.persistence.mountPath }}/etc/security"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/etc/security; + echo -n ${ARTIFACTORY_MASTER_KEY} > {{ .Values.artifactory.persistence.mountPath }}/etc/security/master.key; + {{- end }} + env: + {{- if or .Values.artifactory.joinKey .Values.global.joinKey .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + - name: ARTIFACTORY_JOIN_KEY + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.joinKeySecretName .Values.global.joinKeySecretName }} + name: {{ include "artifactory.joinKeySecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: join-key + {{- end }} + {{- if or .Values.artifactory.jfConnectToken .Values.artifactory.jfConnectSecretName }} + - name: ARTIFACTORY_JFCONNECT_TOKEN + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.jfConnectTokenSecretName }} + name: {{ include "artifactory.jfConnectTokenSecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: jfconnect-token + {{- end }} + {{- if or .Values.artifactory.masterKey .Values.global.masterKey .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + - name: ARTIFACTORY_MASTER_KEY + valueFrom: + secretKeyRef: + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.masterKeySecretName .Values.global.masterKeySecretName }} + name: {{ include "artifactory.masterKeySecretName" . }} + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: master-key + {{- end }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.systemYamlOverride.existingSecret }} + - name: systemyaml + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + {{- if .Values.systemYamlOverride.existingSecret }} + mountPath: "/tmp/etc/{{.Values.systemYamlOverride.dataKey}}" + subPath: {{ .Values.systemYamlOverride.dataKey }} + {{- else }} + mountPath: "/tmp/etc/system.yaml" + subPath: "system.yaml" + {{- end }} + + ######################## Binarystore ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Access config ########################## + {{- if .Values.access.accessConfig }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + - name: access-config + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/access.config.patch.yml" + subPath: "access.config.patch.yml" + {{- end }} + + ######################## Access certs external secret ########################## + {{- if .Values.access.customCertificatesSecretName }} + - name: access-certs + mountPath: "/tmp/etc/tls.crt" + subPath: tls.crt + - name: access-certs + mountPath: "/tmp/etc/tls.key" + subPath: tls.key + {{- end }} + + {{- if or .Values.artifactory.customCertificates.enabled .Values.global.customCertificates.enabled }} + - name: copy-custom-certificates + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory.copyCustomCerts" . | indent 10 }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: ca-certs + mountPath: "/tmp/certs" + {{- end }} + + {{- if .Values.artifactory.circleOfTrustCertificatesSecret }} + - name: copy-circle-of-trust-certificates + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - 'bash' + - '-c' + - > +{{ include "artifactory.copyCircleOfTrustCertsCerts" . | indent 10 }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath }} + - name: circle-of-trust-certs + mountPath: "/tmp/circleoftrustcerts" + {{- end }} + + {{- if .Values.waitForDatabase }} + {{- if .Values.postgresql.enabled }} + - name: "wait-for-db" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.initContainers.resources | indent 10 }} + command: + - /bin/bash + - -c + - | + echo "Waiting for postgresql to come up" + ready=false; + while ! $ready; do echo waiting; + timeout 2s bash -c " + {{- if .Values.artifactory.migration.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.artifactory.migration.preStartCommand . }}; + {{- end }} + scriptsPath="/opt/jfrog/artifactory/app/bin"; + mkdir -p $scriptsPath; + echo "Copy migration scripts and Run migration"; + cp -fv /tmp/migrate.sh $scriptsPath/migrate.sh; + cp -fv /tmp/migrationHelmInfo.yaml $scriptsPath/migrationHelmInfo.yaml; + cp -fv /tmp/migrationStatus.sh $scriptsPath/migrationStatus.sh; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/log; + bash $scriptsPath/migrationStatus.sh {{ include "artifactory.app.version" . }} {{ .Values.artifactory.migration.timeoutSeconds }} > >(tee {{ .Values.artifactory.persistence.mountPath }}/log/helm-migration.log) 2>&1; + env: + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: migration-scripts + mountPath: "/tmp/migrate.sh" + subPath: migrate.sh + - name: migration-scripts + mountPath: "/tmp/migrationHelmInfo.yaml" + subPath: migrationHelmInfo.yaml + - name: migration-scripts + mountPath: "/tmp/migrationStatus.sh" + subPath: migrationStatus.sh + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystore Xml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: "binarystore.xml" + + ######################## Artifactory persistence google storage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + {{- end }} + + ######################## CustomVolumeMounts ########################## + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory.customVolumeMounts" .) . | indent 8 }} + {{- end }} +{{- end }} + {{- if .Values.hostAliases }} + hostAliases: +{{ toYaml .Values.hostAliases | indent 6 }} + {{- end }} + containers: + {{- if .Values.splitServicesToContainers }} + - name: {{ .Values.router.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "router") }} + imagePullPolicy: {{ .Values.router.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/router/app/bin/entrypoint-router.sh + {{- with .Values.router.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_ROUTER_TOPOLOGY_LOCAL_REQUIREDSERVICETYPES + value: {{ include "artifactory.router.requiredServiceTypes" . }} +{{- with .Values.router.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - name: http + containerPort: {{ .Values.router.internalPort }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.router.persistence.mountPath | quote }} +{{- with .Values.router.customVolumeMounts }} +{{ tpl . $ | indent 8 }} +{{- end }} + resources: +{{ toYaml .Values.router.resources | indent 10 }} + {{- if .Values.router.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.router.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.router.readinessProbe.enabled }} + readinessProbe: +{{ tpl .Values.router.readinessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.router.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.router.livenessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.enabled }} + - name: {{ .Values.frontend.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/third-party/node/bin/node /opt/jfrog/artifactory/app/frontend/bin/server/dist/bundle.js /opt/jfrog/artifactory/app/frontend + {{- with .Values.frontend.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if and (gt (.Values.artifactory.replicaCount | int64) 1) (eq (include "artifactory.isImageProType" .) "true") (eq (include "artifactory.isUsingDerby" .) "false") }} + - name : JF_SHARED_NODE_HAENABLED + value: "true" + {{- end }} +{{- with .Values.frontend.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.frontend.resources | indent 10 }} + {{- if .Values.frontend.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.frontend.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.frontend.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.frontend.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.evidence.enabled }} + - name: {{ .Values.evidence.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/evidence/bin/jf-evidence start + {{- with .Values.evidence.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.evidence.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.evidence.internalPort }} + name: http-evidence + - containerPort: {{ .Values.evidence.externalPort }} + name: grpc-evidence + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.evidence.resources | indent 10 }} + {{- if .Values.evidence.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.evidence.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.evidence.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.evidence.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.metadata.enabled }} + - name: {{ .Values.metadata.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/metadata/bin/jf-metadata start + {{- with .Values.metadata.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.metadata.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory.customVolumeMounts" .) . | indent 8 }} + {{- end }} + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.metadata.resources | indent 10 }} + {{- if .Values.metadata.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.metadata.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.metadata.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.metadata.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.event.enabled }} + - name: {{ .Values.event.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/event/bin/jf-event start + {{- with .Values.event.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.event.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.event.resources | indent 10 }} + {{- if .Values.event.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.event.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.event.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.event.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.jfconnect.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" .Values.artifactory.image.repository)) }} + - name: {{ .Values.jfconnect.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/jfconnect/bin/jf-connect start + {{- with .Values.jfconnect.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.jfconnect.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.jfconnect.resources | indent 10 }} + {{- if .Values.jfconnect.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.jfconnect.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.jfconnect.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.jfconnect.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if and .Values.access.enabled (not (.Values.access.runOnArtifactoryTomcat | default false)) }} + - name: {{ .Values.access.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.access.resources }} + resources: +{{ toYaml .Values.access.resources | indent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + set -e; + {{- if .Values.access.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.access.preStartCommand . }}; + {{- end }} + exec /opt/jfrog/artifactory/app/access/bin/entrypoint-access.sh + {{- with .Values.access.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if and (gt (.Values.artifactory.replicaCount | int64) 1) (eq (include "artifactory.isImageProType" .) "true") (eq (include "artifactory.isUsingDerby" .) "false") }} + - name : JF_SHARED_NODE_HAENABLED + value: "true" + {{- end }} + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.access.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence googleStorage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + {{- end }} + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory.customVolumeMounts" .) . | indent 8 }} + {{- end }} + {{- if .Values.access.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.access.startupProbe.config . | indent 10 }} + {{- end }} + {{- if semverCompare " + exec /opt/jfrog/artifactory/app/third-party/java/bin/java {{ .Values.federation.extraJavaOpts }} -jar /opt/jfrog/artifactory/app/rtfs/lib/jf-rtfs + {{- with .Values.federation.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + # TODO - Password,Url,Username - should be derived from env variable +{{- with .Values.federation.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.federation.resources | indent 10 }} + {{- if .Values.federation.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.federation.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.federation.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.federation.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- if .Values.observability.enabled }} + - name: {{ .Values.observability.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + exec /opt/jfrog/artifactory/app/observability/bin/jf-observability start + {{- with .Values.observability.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + - name: JF_SHARED_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name +{{- with .Values.observability.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + volumeMounts: + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + resources: +{{ toYaml .Values.observability.resources | indent 10 }} + {{- if .Values.observability.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.observability.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.observability.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.observability.livenessProbe.config . | indent 10 }} + {{- end }} + {{- end }} + {{- end }} + - name: {{ .Values.artifactory.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "artifactory") }} + imagePullPolicy: {{ .Values.artifactory.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.artifactory.resources }} + resources: +{{ toYaml .Values.artifactory.resources | indent 10 }} + {{- end }} + command: + - '/bin/bash' + - '-c' + - > + set -e; + if [ -d /artifactory_extra_conf ] && [ -d /artifactory_bootstrap ]; then + echo "Copying bootstrap config from /artifactory_extra_conf to /artifactory_bootstrap"; + cp -Lrfv /artifactory_extra_conf/ /artifactory_bootstrap/; + fi; + {{- if .Values.artifactory.configMapName }} + echo "Copying bootstrap configs"; + cp -Lrf /bootstrap/* /artifactory_bootstrap/; + {{- end }} + {{- if .Values.artifactory.userPluginSecrets }} + echo "Copying plugins"; + cp -Lrf /tmp/plugin/*/* /artifactory_bootstrap/plugins; + {{- end }} + {{- range .Values.artifactory.copyOnEveryStartup }} + {{- $targetPath := printf "%s/%s" $.Values.artifactory.persistence.mountPath .target }} + {{- $baseDirectory := regexFind ".*/" $targetPath }} + mkdir -p {{ $baseDirectory }}; + cp -Lrf {{ .source }} {{ $.Values.artifactory.persistence.mountPath }}/{{ .target }}; + {{- end }} + {{- if .Values.artifactory.preStartCommand }} + echo "Running custom preStartCommand command"; + {{ tpl .Values.artifactory.preStartCommand . }}; + {{- end }} + exec /entrypoint-artifactory.sh + {{- with .Values.artifactory.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + env: + {{- if and (gt (.Values.artifactory.replicaCount | int64) 1) (eq (include "artifactory.isImageProType" .) "true") (eq (include "artifactory.isUsingDerby" .) "false") }} + - name : JF_SHARED_NODE_HAENABLED + value: "true" + {{- end }} + {{- if .Values.aws.license.enabled }} + - name: IS_AWS_LICENSE + value: "true" + - name: AWS_REGION + value: {{ .Values.aws.region | quote }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: AWS_WEB_IDENTITY_REFRESH_TOKEN_FILE + value: "/var/run/secrets/product-license/license_token" + - name: AWS_ROLE_ARN + valueFrom: + secretKeyRef: + name: {{ .Values.aws.licenseConfigSecretName }} + key: iam_role + {{- end }} + {{- end }} + {{- if .Values.splitServicesToContainers }} + - name : JF_ROUTER_ENABLED + value: "true" + - name : JF_ROUTER_SERVICE_ENABLED + value: "false" + - name : JF_EVENT_ENABLED + value: "false" + - name : JF_METADATA_ENABLED + value: "false" + - name : JF_FRONTEND_ENABLED + value: "false" + - name: JF_FEDERATION_ENABLED + value: "false" + - name : JF_OBSERVABILITY_ENABLED + value: "false" + - name : JF_JFCONNECT_SERVICE_ENABLED + value: "false" + - name : JF_EVIDENCE_ENABLED + value: "false" + {{- if not (.Values.access.runOnArtifactoryTomcat | default false) }} + - name : JF_ACCESS_ENABLED + value: "false" + {{- end}} + {{- end}} + {{- if and (not .Values.waitForDatabase) (not .Values.postgresql.enabled) }} + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + {{- end }} + {{- if or .Values.database.secrets.user .Values.database.user }} + - name: JF_SHARED_DATABASE_USERNAME + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.user }} + name: {{ tpl .Values.database.secrets.user.name . }} + key: {{ tpl .Values.database.secrets.user.key . }} + {{- else if .Values.database.user }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-user + {{- end }} + {{- end }} + {{ if or .Values.database.secrets.password .Values.database.password .Values.postgresql.enabled }} + - name: JF_SHARED_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.password }} + name: {{ tpl .Values.database.secrets.password.name . }} + key: {{ tpl .Values.database.secrets.password.key . }} + {{- else if .Values.database.password }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-password + {{- else if .Values.postgresql.enabled }} + name: {{ .Release.Name }}-postgresql + key: postgresql-password + {{- end }} + {{- end }} + {{- if or .Values.database.secrets.url .Values.database.url }} + - name: JF_SHARED_DATABASE_URL + valueFrom: + secretKeyRef: + {{- if .Values.database.secrets.url }} + name: {{ tpl .Values.database.secrets.url.name . }} + key: {{ tpl .Values.database.secrets.url.key . }} + {{- else if .Values.database.url }} + {{- if not .Values.artifactory.unifiedSecretInstallation }} + name: {{ template "artifactory.fullname" . }}-database-creds + {{- else }} + name: "{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret" + {{- end }} + key: db-url + {{- end }} + {{- end }} +{{- with .Values.artifactory.extraEnvironmentVariables }} +{{ tpl (toYaml .) $ | indent 8 }} +{{- end }} + ports: + - containerPort: {{ .Values.artifactory.internalPort }} + name: http + - containerPort: {{ .Values.artifactory.internalArtifactoryPort }} + name: http-internal + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- if .Values.artifactory.javaOpts.jmx.enabled }} + - containerPort: {{ .Values.artifactory.javaOpts.jmx.port }} + name: tcp-jmx + {{- end }} + {{- if .Values.artifactory.ssh.enabled }} + - containerPort: {{ .Values.artifactory.ssh.internalPort }} + name: tcp-ssh + {{- end }} + volumeMounts: + {{- if .Values.artifactory.customPersistentVolumeClaim }} + - name: {{ .Values.artifactory.customPersistentVolumeClaim.name }} + mountPath: {{ .Values.artifactory.customPersistentVolumeClaim.mountPath }} + {{- end }} + {{- if .Values.aws.licenseConfigSecretName }} + - name: awsmp-product-license + mountPath: "/var/run/secrets/product-license" + {{- end }} + {{- if .Values.artifactory.userPluginSecrets }} + - name: bootstrap-plugins + mountPath: "/artifactory_bootstrap/plugins/" + {{- range .Values.artifactory.userPluginSecrets }} + - name: {{ tpl . $ }} + mountPath: "/tmp/plugin/{{ tpl . $ }}" + {{- end }} + {{- end }} + - name: artifactory-volume + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + + ######################## Artifactory config map ########################## + {{- if .Values.artifactory.configMapName }} + - name: bootstrap-config + mountPath: "/bootstrap/" + {{- end }} + + ######################## Artifactory persistence nfs ########################## + {{- if eq .Values.artifactory.persistence.type "nfs" }} + - name: artifactory-data + mountPath: "{{ .Values.artifactory.persistence.nfs.dataDir }}" + - name: artifactory-backup + mountPath: "{{ .Values.artifactory.persistence.nfs.backupDir }}" + {{- else }} + + ######################## Artifactory persistence binarystoreXml ########################## + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.customBinarystoreXmlSecret }} + - name: binarystore-xml + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/tmp/etc/artifactory/binarystore.xml" + subPath: binarystore.xml + + ######################## Artifactory persistence googleStorage ########################## + {{- if .Values.artifactory.persistence.googleStorage.gcpServiceAccount.enabled }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.persistence.googleStorage.gcpServiceAccount.customSecretName }} + - name: gcpcreds-json + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/gcp.credentials.json" + subPath: gcp.credentials.json + {{- end }} + {{- end }} + + ######################## Artifactory license ########################## + {{- if or .Values.artifactory.license.secret .Values.artifactory.license.licenseKey }} + {{- if or (not .Values.artifactory.unifiedSecretInstallation) .Values.artifactory.license.secret }} + - name: artifactory-license + {{- else }} + - name: {{ include "artifactory.unifiedCustomSecretVolumeName" . }} + {{- end }} + mountPath: "/artifactory_bootstrap/artifactory.cluster.license" + {{- if .Values.artifactory.license.secret }} + subPath: {{ .Values.artifactory.license.dataKey }} + {{- else if .Values.artifactory.license.licenseKey }} + subPath: artifactory.lic + {{- end }} + {{- end }} + + - name: installer-info + mountPath: "/artifactory_bootstrap/info/installer-info.json" + subPath: installer-info.json + {{- if or .Values.artifactory.customVolumeMounts .Values.global.customVolumeMounts }} +{{ tpl (include "artifactory.customVolumeMounts" .) . | indent 8 }} + {{- end }} + {{- if .Values.artifactory.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.artifactory.startupProbe.config . | indent 10 }} + {{- end }} + {{- if and (not .Values.splitServicesToContainers) (semverCompare "=1.18.0-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.defaultBackend.enabled }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + defaultBackend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- else }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} + {{- end }} + rules: +{{- if .Values.ingress.hosts }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingress.routerPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- if not $.Values.ingress.disableRouterBypass }} + - path: {{ $.Values.ingress.artifactoryPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $artifactoryServicePort }} + {{- end }} + {{- if and $.Values.federation.enabled (not (regexMatch "^.*(oss|cpp-ce|jcr).*$" $.Values.artifactory.image.repository)) }} + - path: {{ $.Values.ingress.rtfsPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $.Values.federation.internalPort }} + {{- end }} + {{- end }} + {{- else }} + {{- range $host := .Values.ingress.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingress.routerPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- if not $.Values.ingress.disableRouterBypass }} + - path: {{ $.Values.ingress.artifactoryPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $artifactoryServicePort }} + {{- end }} + {{- end }} + {{- end }} +{{- end -}} + {{- with .Values.ingress.additionalRules }} +{{ tpl . $ | indent 2 }} + {{- end }} + + {{- if .Values.ingress.tls }} + tls: +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end -}} + +{{- if .Values.customIngress }} +--- +{{ .Values.customIngress | toYaml | trimSuffix "\n" }} +{{- end -}} +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/logger-configmap.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/logger-configmap.yaml new file mode 100644 index 0000000000..41a078b024 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/logger-configmap.yaml @@ -0,0 +1,63 @@ +{{- if or .Values.artifactory.loggers .Values.artifactory.catalinaLoggers }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory.fullname" . }}-logger + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + tail-log.sh: | + #!/bin/sh + + LOG_DIR=$1 + LOG_NAME=$2 + PID= + + # Wait for log dir to appear + while [ ! -d ${LOG_DIR} ]; do + sleep 1 + done + + cd ${LOG_DIR} + + LOG_PREFIX=$(echo ${LOG_NAME} | sed 's/.log$//g') + + # Find the log to tail + LOG_FILE=$(ls -1t ./${LOG_PREFIX}.log 2>/dev/null) + + # Wait for the log file + while [ -z "${LOG_FILE}" ]; do + sleep 1 + LOG_FILE=$(ls -1t ./${LOG_PREFIX}.log 2>/dev/null) + done + + echo "Log file ${LOG_FILE} is ready!" + + # Get inode number + INODE_ID=$(ls -i ${LOG_FILE}) + + # echo "Tailing ${LOG_FILE}" + tail -F ${LOG_FILE} & + PID=$! + + # Loop forever to see if a new log was created + while true; do + # Check inode number + NEW_INODE_ID=$(ls -i ${LOG_FILE}) + + # If inode number changed, this means log was rotated and need to start a new tail + if [ "${INODE_ID}" != "${NEW_INODE_ID}" ]; then + kill -9 ${PID} 2>/dev/null + INODE_ID="${NEW_INODE_ID}" + + # Start a new tail + tail -F ${LOG_FILE} & + PID=$! + fi + sleep 1 + done + +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-artifactory-conf.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-artifactory-conf.yaml new file mode 100644 index 0000000000..3434489943 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-artifactory-conf.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.nginx.customArtifactoryConfigMap) .Values.nginx.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory.fullname" . }}-nginx-artifactory-conf + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + artifactory.conf: | +{{- if .Values.nginx.artifactoryConf }} +{{ tpl .Values.nginx.artifactoryConf . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/nginx-artifactory-conf.yaml" ) . | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-certificate-secret.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-certificate-secret.yaml new file mode 100644 index 0000000000..f13d401747 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-certificate-secret.yaml @@ -0,0 +1,14 @@ +{{- if and (not .Values.nginx.tlsSecretName) .Values.nginx.enabled .Values.nginx.https.enabled }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: {{ template "artifactory.fullname" . }}-nginx-certificate + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: +{{ ( include "artifactory.gen-certs" . ) | indent 2 }} +{{- end }} diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-conf.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-conf.yaml new file mode 100644 index 0000000000..31219d58a9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-conf.yaml @@ -0,0 +1,18 @@ +{{- if and (not .Values.nginx.customConfigMap) .Values.nginx.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "artifactory.fullname" . }}-nginx-conf + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + nginx.conf: | +{{- if .Values.nginx.mainConf }} +{{ tpl .Values.nginx.mainConf . | indent 4 }} +{{- else }} +{{ tpl ( .Files.Get "files/nginx-main-conf.yaml" ) . | indent 4 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-deployment.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-deployment.yaml new file mode 100644 index 0000000000..774bedccaf --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-deployment.yaml @@ -0,0 +1,223 @@ +{{- if .Values.nginx.enabled -}} +{{- $serviceName := include "artifactory.fullname" . -}} +{{- $servicePort := .Values.artifactory.externalPort -}} +apiVersion: apps/v1 +kind: {{ .Values.nginx.kind }} +metadata: + name: {{ template "artifactory.nginx.fullname" . }} + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + component: {{ .Values.nginx.name }} +{{- if .Values.nginx.labels }} +{{ toYaml .Values.nginx.labels | indent 4 }} +{{- end }} +{{- with .Values.nginx.deployment.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if eq .Values.nginx.kind "StatefulSet" }} + serviceName: {{ template "artifactory.nginx.fullname" . }} +{{- end }} +{{- if ne .Values.nginx.kind "DaemonSet" }} + replicas: {{ .Values.nginx.replicaCount }} +{{- end }} + selector: + matchLabels: + app: {{ template "artifactory.name" . }} + release: {{ .Release.Name }} + component: {{ .Values.nginx.name }} + template: + metadata: + annotations: + checksum/nginx-conf: {{ include (print $.Template.BasePath "/nginx-conf.yaml") . | sha256sum }} + checksum/nginx-artifactory-conf: {{ include (print $.Template.BasePath "/nginx-artifactory-conf.yaml") . | sha256sum }} + {{- range $key, $value := .Values.nginx.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + component: {{ .Values.nginx.name }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- if .Values.nginx.labels }} +{{ toYaml .Values.nginx.labels | indent 8 }} +{{- end }} + spec: + {{- if .Values.nginx.podSecurityContext.enabled }} + securityContext: {{- omit .Values.nginx.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "artifactory.serviceAccountName" . }} + terminationGracePeriodSeconds: {{ .Values.nginx.terminationGracePeriodSeconds }} + {{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} +{{- include "artifactory.imagePullSecrets" . | indent 6 }} + {{- end }} + {{- if .Values.nginx.priorityClassName }} + priorityClassName: {{ .Values.nginx.priorityClassName | quote }} + {{- end }} + {{- if .Values.nginx.topologySpreadConstraints }} + topologySpreadConstraints: +{{ tpl (toYaml .Values.nginx.topologySpreadConstraints) . | indent 8 }} + {{- end }} + initContainers: + {{- if .Values.nginx.customInitContainers }} +{{ tpl (include "artifactory.nginx.customInitContainers" .) . | indent 6 }} + {{- end }} + - name: "setup" + image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + command: + - '/bin/sh' + - '-c' + - > + rm -rfv {{ .Values.nginx.persistence.mountPath }}/lost+found; + mkdir -p {{ .Values.nginx.persistence.mountPath }}/logs; + resources: + {{- toYaml .Values.initContainers.resources | nindent 10 }} + volumeMounts: + - mountPath: {{ .Values.nginx.persistence.mountPath | quote }} + name: nginx-volume + containers: + - name: {{ .Values.nginx.name }} + image: {{ include "artifactory.getImageInfoByValue" (list . "nginx") }} + imagePullPolicy: {{ .Values.nginx.image.pullPolicy }} + {{- if .Values.nginx.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.nginx.containerSecurityContext "enabled" | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.nginx.customCommand }} + command: +{{- tpl (include "nginx.command" .) . | indent 10 }} + {{- end }} + ports: +{{ if .Values.nginx.customPorts }} +{{ toYaml .Values.nginx.customPorts | indent 8 }} +{{ end }} + # DEPRECATION NOTE: The following is to maintain support for values pre 1.3.1 and + # will be cleaned up in a later version + {{- if .Values.nginx.http }} + {{- if .Values.nginx.http.enabled }} + - containerPort: {{ .Values.nginx.http.internalPort }} + name: http + {{- end }} + {{- else }} # DEPRECATED + - containerPort: {{ .Values.nginx.internalPortHttp }} + name: http-internal + {{- end }} + {{- if .Values.nginx.https }} + {{- if .Values.nginx.https.enabled }} + - containerPort: {{ .Values.nginx.https.internalPort }} + name: https + {{- end }} + {{- else }} # DEPRECATED + - containerPort: {{ .Values.nginx.internalPortHttps }} + name: https-internal + {{- end }} + {{- if .Values.artifactory.ssh.enabled }} + - containerPort: {{ .Values.nginx.ssh.internalPort }} + name: tcp-ssh + {{- end }} + {{- with .Values.nginx.lifecycle }} + lifecycle: +{{ toYaml . | indent 10 }} + {{- end }} + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-artifactory-conf + mountPath: "{{ .Values.nginx.persistence.mountPath }}/conf.d/" + - name: nginx-volume + mountPath: {{ .Values.nginx.persistence.mountPath | quote }} + {{- if .Values.nginx.https.enabled }} + - name: ssl-certificates + mountPath: "{{ .Values.nginx.persistence.mountPath }}/ssl" + {{- end }} + {{- if .Values.nginx.customVolumeMounts }} +{{ tpl (include "artifactory.nginx.customVolumeMounts" .) . | indent 8 }} + {{- end }} + resources: +{{ toYaml .Values.nginx.resources | indent 10 }} + {{- if .Values.nginx.startupProbe.enabled }} + startupProbe: +{{ tpl .Values.nginx.startupProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.nginx.readinessProbe.enabled }} + readinessProbe: +{{ tpl .Values.nginx.readinessProbe.config . | indent 10 }} + {{- end }} + {{- if .Values.nginx.livenessProbe.enabled }} + livenessProbe: +{{ tpl .Values.nginx.livenessProbe.config . | indent 10 }} + {{- end }} + {{- $mountPath := .Values.nginx.persistence.mountPath }} + {{- range .Values.nginx.loggers }} + - name: {{ . | replace "_" "-" | replace "." "-" }} + image: {{ include "artifactory.getImageInfoByValue" (list $ "initContainers") }} + imagePullPolicy: {{ $.Values.initContainers.image.pullPolicy }} + command: + - tail + args: + - '-F' + - '{{ $mountPath }}/logs/{{ . }}' + volumeMounts: + - name: nginx-volume + mountPath: {{ $mountPath }} + resources: +{{ toYaml $.Values.nginx.loggersResources | indent 10 }} + {{- end }} + {{- if .Values.nginx.customSidecarContainers }} +{{ tpl (include "artifactory.nginx.customSidecarContainers" .) . | indent 6 }} + {{- end }} + {{- if or .Values.nginx.nodeSelector .Values.global.nodeSelector }} +{{ tpl (include "nginx.nodeSelector" .) . | indent 6 }} + {{- end }} + {{- with .Values.nginx.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + {{- if .Values.nginx.customVolumes }} +{{ tpl (include "artifactory.nginx.customVolumes" .) . | indent 6 }} + {{- end }} + - name: nginx-conf + configMap: + {{- if .Values.nginx.customConfigMap }} + name: {{ .Values.nginx.customConfigMap }} + {{- else }} + name: {{ template "artifactory.fullname" . }}-nginx-conf + {{- end }} + - name: nginx-artifactory-conf + configMap: + {{- if .Values.nginx.customArtifactoryConfigMap }} + name: {{ .Values.nginx.customArtifactoryConfigMap }} + {{- else }} + name: {{ template "artifactory.fullname" . }}-nginx-artifactory-conf + {{- end }} + - name: nginx-volume + {{- if .Values.nginx.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.nginx.persistence.existingClaim | default (include "artifactory.nginx.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.nginx.https.enabled }} + - name: ssl-certificates + secret: + {{- if .Values.nginx.tlsSecretName }} + secretName: {{ .Values.nginx.tlsSecretName }} + {{- else }} + secretName: {{ template "artifactory.fullname" . }}-nginx-certificate + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-pdb.yaml b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-pdb.yaml new file mode 100644 index 0000000000..dff0c23a3e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/charts/artifactory/templates/nginx-pdb.yaml @@ -0,0 +1,23 @@ +{{- if .Values.nginx.enabled -}} +{{- if semverCompare "; + # kubernetes.io/tls-acme: "true" + # nginx.ingress.kubernetes.io/proxy-body-size: "0" + labels: {} + # traffic-type: external + # traffic-type: internal + tls: [] + ## Secrets must be manually created in the namespace. + # - secretName: chart-example-tls + # hosts: + # - artifactory.domain.example + + ## Additional ingress rules + additionalRules: [] + ## This is an experimental feature, enabling this feature will route all traffic through the Router. + disableRouterBypass: false +## Allows to add custom ingress +customIngress: "" +networkpolicy: [] +## Allows all ingress and egress +# - name: artifactory +# podSelector: +# matchLabels: +# app: artifactory +# egress: +# - {} +# ingress: +# - {} +## Uncomment to allow only artifactory pods to communicate with postgresql (if postgresql.enabled is true) +# - name: postgresql +# podSelector: +# matchLabels: +# app: postgresql +# ingress: +# - from: +# - podSelector: +# matchLabels: +# app: artifactory + +## Apply horizontal pod auto scaling on artifactory pods +## Ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 70 +## You can use a pre-existing secret with keys license_token and iam_role by specifying licenseConfigSecretName +## Example : Create a generic secret using `kubectl create secret generic --from-literal=license_token=${TOKEN} --from-literal=iam_role=${ROLE_ARN}` +aws: + license: + enabled: false + licenseConfigSecretName: + region: us-east-1 +## Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled Enabled containers' Security Context +## @param containerSecurityContext.runAsNonRoot Set container's Security Context runAsNonRoot +## @param containerSecurityContext.privileged Set container's Security Context privileged +## @param containerSecurityContext.allowPrivilegeEscalation Set container's Security Context allowPrivilegeEscalation +## @param containerSecurityContext.capabilities.drop List of capabilities to be dropped +## @param containerSecurityContext.seccompProfile.type Set container's Security Context seccomp profile +## +containerSecurityContext: + enabled: true + runAsNonRoot: true + privileged: false + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL +## The following router settings are to configure only when splitServicesToContainers set to true +router: + name: router + image: + registry: releases-docker.jfrog.io + repository: jfrog/router + tag: 7.118.0 + pullPolicy: IfNotPresent + serviceRegistry: + ## Service registry (Access) TLS verification skipped if enabled + insecure: false + internalPort: 8082 + externalPort: 8082 + tlsEnabled: false + ## Extra environment variables that can be used to tune router to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: MY_ENV_VAR + # value: "" + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "1Gi" + # cpu: "1" + + ## Add lifecycle hooks for router container + lifecycle: + ## From Artifactory versions 7.52.x, Wait for Artifactory to complete any open uploads or downloads before terminating + preStop: + exec: + command: ["sh", "-c", "while [[ $(curl --fail --silent --connect-timeout 2 http://localhost:8081/artifactory/api/v1/system/liveness) =~ OK ]]; do echo Artifactory is still alive; sleep 2; done"] + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + ## Add custom volumesMounts + customVolumeMounts: | + # - name: custom-script + # mountPath: /scripts/script.sh + # subPath: script.sh + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} {{ include "artifactory.scheme" . }}://localhost:{{ .Values.router.internalPort }}/router/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " prepended. + unifiedSecretPrependReleaseName: true + ## For HA installation, set this value > 1. This is only supported in Artifactory 7.25.x (appVersions) and above. + replicaCount: 1 + # minAvailable: 1 + + ## Note that by default we use appVersion to get image tag/version + image: + registry: releases-docker.jfrog.io + repository: jfrog/artifactory-pro + # tag: + pullPolicy: IfNotPresent + labels: {} + updateStrategy: + type: RollingUpdate + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + schedulerName: + ## Create a priority class for the Artifactory pod or use an existing one + ## NOTE - Maximum allowed value of a user defined priority is 1000000000 + priorityClass: + create: false + value: 1000000000 + ## Override default name + # name: + ## Use an existing priority class + # existingPriorityClass: + ## Spread Artifactory pods evenly across your nodes or some other topology + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app: '{{ template "artifactory.name" . }}' + # role: '{{ template "artifactory.name" . }}' + # release: "{{ .Release.Name }}" + + ## Delete the db.properties file in ARTIFACTORY_HOME/etc/db.properties + deleteDBPropertiesOnStartup: true + ## certificates added to this secret will be copied to $JFROG_HOME/artifactory/var/etc/security/keys/trusted directory + customCertificates: + enabled: false + # certificateSecretName: + database: + maxOpenConnections: 80 + tomcat: + maintenanceConnector: + port: 8091 + connector: + maxThreads: 200 + sendReasonPhrase: false + extraConfig: 'acceptCount="400"' + ## Support for metrics is only available for Artifactory 7.7.x (appVersions) and above. + ## To enable set `.Values.artifactory.metrics.enabled` to `true` + ## Note : Depricated openMetrics as part of 7.87.x and renamed to `metrics` + ## Refer - https://www.jfrog.com/confluence/display/JFROG/Open+Metrics + metrics: + enabled: false + ## Settings for pushing metrics to Insight - enable filebeat to true + filebeat: + enabled: false + log: + enabled: false + ## Log level for filebeat. Possible values: debug, info, warning, or error. + level: "info" + ## Elasticsearch details for filebeat to connect + elasticsearch: + url: "Elasticsearch url where JFrog Insight is installed For example, http://:8082" + username: "" + password: "" + ## Support for Cold Artifact Storage + ## set 'coldStorage.enabled' to 'true' only for Artifactory instance that you are designating as the Cold instance + ## Refer - https://jfrog.com/help/r/jfrog-platform-administration-documentation/setting-up-cold-artifact-storage + coldStorage: + enabled: false + ## This directory is intended for use with NFS eventual configuration for HA + haDataDir: + enabled: false + path: + haBackupDir: + enabled: false + path: + ## Files to copy to ARTIFACTORY_HOME/ on each Artifactory startup + ## Note : From 107.46.x chart versions, copyOnEveryStartup is not needed for binarystore.xml, it is always copied via initContainers + copyOnEveryStartup: + ## Absolute path + # - source: /artifactory_bootstrap/artifactory.lic + ## Relative to ARTIFACTORY_HOME/ + # target: etc/artifactory/ + + ## Sidecar containers for tailing Artifactory logs + loggers: [] + # - access-audit.log + # - access-request.log + # - access-security-audit.log + # - access-service.log + # - artifactory-access.log + # - artifactory-event.log + # - artifactory-import-export.log + # - artifactory-request.log + # - artifactory-service.log + # - frontend-request.log + # - frontend-service.log + # - metadata-request.log + # - metadata-service.log + # - router-request.log + # - router-service.log + # - router-traefik.log + # - derby.log + + ## Loggers containers resources + loggersResources: {} + # requests: + # memory: "10Mi" + # cpu: "10m" + # limits: + # memory: "100Mi" + # cpu: "50m" + + ## Sidecar containers for tailing Tomcat (catalina) logs + catalinaLoggers: [] + # - tomcat-catalina.log + # - tomcat-localhost.log + + ## Tomcat (catalina) loggers resources + catalinaLoggersResources: {} + # requests: + # memory: "10Mi" + # cpu: "10m" + # limits: + # memory: "100Mi" + # cpu: "50m" + + ## Migration support from 6.x to 7.x + migration: + enabled: false + timeoutSeconds: 3600 + ## Extra pre-start command in migration Init Container to install JDBC driver for MySql/MariaDb/Oracle + # preStartCommand: "mkdir -p /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib; cd /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib && curl -o /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib/mysql-connector-java-5.1.41.jar https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.41/mysql-connector-java-5.1.41.jar" + ## Add custom init containers execution before predefined init containers + customInitContainersBegin: | + # - name: "custom-setup" + # image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'touch {{ .Values.artifactory.persistence.mountPath }}/example-custom-setup' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: artifactory-volume + ## Add custom init containers execution after predefined init containers + customInitContainers: | + # - name: "custom-systemyaml-setup" + # image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'curl -o {{ .Values.artifactory.persistence.mountPath }}/etc/system.yaml https:///systemyaml' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: artifactory-volume + ## Add custom sidecar containers + ## - The provided example uses a custom volume (customVolumes) + customSidecarContainers: | + # - name: "sidecar-list-etc" + # image: {{ include "artifactory.getImageInfoByValue" (list . "initContainers") }} + # imagePullPolicy: {{ .Values.initContainers.image.pullPolicy }} + # securityContext: + # runAsNonRoot: true + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - NET_RAW + # command: + # - 'sh' + # - '-c' + # - 'sh /scripts/script.sh' + # volumeMounts: + # - mountPath: "{{ .Values.artifactory.persistence.mountPath }}" + # name: artifactory-volume + # - mountPath: "/scripts/script.sh" + # name: custom-script + # subPath: script.sh + # resources: + # requests: + # memory: "32Mi" + # cpu: "50m" + # limits: + # memory: "128Mi" + # cpu: "100m" + ## Add custom volumes + ## If .Values.artifactory.unifiedSecretInstallation is true then secret name should be '{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret' + customVolumes: | + # - name: custom-script + # configMap: + # name: custom-script + ## Add custom volumesMounts + customVolumeMounts: | + # - name: custom-script + # mountPath: "/scripts/script.sh" + # subPath: script.sh + # - name: posthook-start + # mountPath: "/scripts/posthoook-start.sh" + # subPath: posthoook-start.sh + # - name: prehook-start + # mountPath: "/scripts/prehook-start.sh" + # subPath: prehook-start.sh + ## Add custom persistent volume mounts - Available to the entire namespace + customPersistentVolumeClaim: {} + # name: + # mountPath: + # accessModes: + # - "-" + # size: + # storageClassName: + + ## Artifactory license. + license: + ## licenseKey is the license key in plain text. Use either this or the license.secret setting + licenseKey: + ## If artifactory.license.secret is passed, it will be mounted as + ## ARTIFACTORY_HOME/etc/artifactory.lic and loaded at run time. + secret: + ## The dataKey should be the name of the secret data key created. + dataKey: + ## Create configMap with artifactory.config.import.xml and security.import.xml and pass name of configMap in following parameter + configMapName: + ## Add any list of configmaps to Artifactory + configMaps: | + # posthook-start.sh: |- + # echo "This is a post start script" + # posthook-end.sh: |- + # echo "This is a post end script" + ## List of secrets for Artifactory user plugins. + ## One Secret per plugin's files. + userPluginSecrets: + # - archive-old-artifacts + # - build-cleanup + # - webhook + # - '{{ template "my-chart.fullname" . }}' + + ## Artifactory requires a unique master key. + ## You can generate one with the command: "openssl rand -hex 32" + ## An initial one is auto generated by Artifactory on first startup. + # masterKey: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + ## Alternatively, you can use a pre-existing secret with a key called master-key by specifying masterKeySecretName + # masterKeySecretName: + + ## Join Key to connect other services to Artifactory + ## IMPORTANT: Setting this value overrides the existing joinKey + ## IMPORTANT: You should NOT use the example joinKey for a production deployment! + # joinKey: EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + ## Alternatively, you can use a pre-existing secret with a key called join-key by specifying joinKeySecretName + # joinKeySecretName: + + ## Registration Token for JFConnect + # jfConnectToken: + ## Alternatively, you can use a pre-existing secret with a key called jfconnect-token by specifying jfConnectTokenSecretName + # jfConnectTokenSecretName: + + ## Add custom secrets - secret per file + ## If .Values.artifactory.unifiedSecretInstallation is true then secret name should be '{{ template "artifactory.unifiedSecretPrependReleaseName" . }}-unified-secret' common to all secrets + customSecrets: + # - name: custom-secret + # key: custom-secret.yaml + # data: > + # custom_secret_config: + # parameter1: value1 + # parameter2: value2 + # - name: custom-secret2 + # key: custom-secret2.config + # data: | + # here the custom secret 2 config + + ## If false, all service console logs will not redirect to a common console.log + consoleLog: false + ## admin allows to set the password for the default admin user. + ## See: https://www.jfrog.com/confluence/display/JFROG/Users+and+Groups#UsersandGroups-RecreatingtheDefaultAdminUserrecreate + admin: + ip: "127.0.0.1" + username: "admin" + password: + secret: + dataKey: + ## Extra pre-start command to install JDBC driver for MySql/MariaDb/Oracle + # preStartCommand: "mkdir -p /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib; cd /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib && curl -o /opt/jfrog/artifactory/var/bootstrap/artifactory/tomcat/lib/mysql-connector-java-5.1.41.jar https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.41/mysql-connector-java-5.1.41.jar" + + ## Add lifecycle hooks for artifactory container + lifecycle: {} + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + # preStop: + # exec: + # command: ["/bin/sh","-c","echo Hello from the preStop handler"] + + ## Extra environment variables that can be used to tune Artifactory to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: SERVER_XML_ARTIFACTORY_PORT + # value: "8081" + # - name: SERVER_XML_ARTIFACTORY_MAX_THREADS + # value: "200" + # - name: SERVER_XML_ACCESS_MAX_THREADS + # value: "50" + # - name: SERVER_XML_ARTIFACTORY_EXTRA_CONFIG + # value: "" + # - name: SERVER_XML_ACCESS_EXTRA_CONFIG + # value: "" + # - name: SERVER_XML_EXTRA_CONNECTOR + # value: "" + # - name: DB_POOL_MAX_ACTIVE + # value: "100" + # - name: DB_POOL_MAX_IDLE + # value: "10" + # - name: MY_SECRET_ENV_VAR + # valueFrom: + # secretKeyRef: + # name: my-secret-name + # key: my-secret-key + + ## System YAML entries now reside under files/system.yaml. + ## You can provide the specific values that you want to add or override under 'artifactory.extraSystemYaml'. + ## For example: + ## extraSystemYaml: + ## shared: + ## node: + ## id: my-instance + ## The entries provided under 'artifactory.extraSystemYaml' are merged with files/system.yaml to create the final system.yaml. + ## If you have already provided system.yaml under, 'artifactory.systemYaml', the values in that entry take precedence over files/system.yaml + ## You can modify specific entries with your own value under `artifactory.extraSystemYaml`, The values under extraSystemYaml overrides the values under 'artifactory.systemYaml' and files/system.yaml + extraSystemYaml: {} + ## systemYaml is intentionally commented and the previous content has been moved under files/system.yaml. + ## You have to add the all entries of the system.yaml file here, and it overrides the values in files/system.yaml. + # systemYaml: + annotations: {} + service: + name: artifactory + type: ClusterIP + ## @param service.ipFamilyPolicy Controller Service ipFamilyPolicy (optional, cloud specific) + ## This can be either SingleStack, PreferDualStack or RequireDualStack + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilyPolicy: "" + ## @param service.ipFamilies Controller Service ipFamilies (optional, cloud specific) + ## This can be either ["IPv4"], ["IPv6"], ["IPv4", "IPv6"] or ["IPv6", "IPv4"] + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilies: [] + ## For supporting whitelist on the Artifactory service (useful if setting service.type=LoadBalancer) + ## Set this to a list of IP CIDR ranges + ## Example: loadBalancerSourceRanges: ['10.10.10.5/32', '10.11.10.5/32'] + ## or pass from helm command line + ## Example: helm install ... --set nginx.service.loadBalancerSourceRanges='{10.10.10.5/32,10.11.10.5/32}' + loadBalancerSourceRanges: [] + annotations: {} + ## If the type is NodePort you can set a fixed port + # nodePort: 32082 + statefulset: + annotations: {} + ## IMPORTANT: If overriding artifactory.internalPort: + ## DO NOT use port lower than 1024 as Artifactory runs as non-root and cannot bind to ports lower than 1024! + externalPort: 8082 + internalPort: 8082 + externalArtifactoryPort: 8081 + internalArtifactoryPort: 8081 + terminationGracePeriodSeconds: 30 + ## Pod Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## @param artifactory.podSecurityContext.enabled Enable security context + ## @param artifactory.podSecurityContext.runAsNonRoot Set pod's Security Context runAsNonRoot + ## @param artifactory.podSecurityContext.runAsUser User ID for the pod + ## @param artifactory.podSecurityContext.runASGroup Group ID for the pod + ## @param artifactory.podSecurityContext.fsGroup Group ID for the pod + ## + podSecurityContext: + enabled: true + runAsNonRoot: true + runAsUser: 1030 + runAsGroup: 1030 + fsGroup: 1030 + # fsGroupChangePolicy: "Always" + # seLinuxOptions: {} + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:{{ .Values.artifactory.tomcat.maintenanceConnector.port }}/artifactory/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare "", + # "private_key_id": "?????", + # "private_key": "-----BEGIN PRIVATE KEY-----\n????????==\n-----END PRIVATE KEY-----\n", + # "client_email": "???@j.iam.gserviceaccount.com", + # "client_id": "???????", + # "auth_uri": "https://accounts.google.com/o/oauth2/auth", + # "token_uri": "https://oauth2.googleapis.com/token", + # "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + # "client_x509_cert_url": "https://www.googleapis.com/robot/v1....." + # } + endpoint: commondatastorage.googleapis.com + httpsOnly: false + ## Set a unique bucket name + bucketName: "artifactory-gcp" + ## GCP Bucket Authentication with Identity and Credential is deprecated. + ## identity: + ## credential: + path: "artifactory/filestore" + bucketExists: false + useInstanceCredentials: false + enableSignedUrlRedirect: false + ## For artifactory.persistence.type aws-s3-v3, s3-storage-v3-direct, cluster-s3-storage-v3, s3-storage-v3-archive + awsS3V3: + testConnection: false + identity: + credential: + region: + bucketName: artifactory-aws + path: artifactory/filestore + endpoint: + port: + useHttp: + maxConnections: 50 + connectionTimeout: + socketTimeout: + kmsServerSideEncryptionKeyId: + kmsKeyRegion: + kmsCryptoMode: + useInstanceCredentials: true + usePresigning: false + signatureExpirySeconds: 300 + signedUrlExpirySeconds: 30 + cloudFrontDomainName: + cloudFrontKeyPairId: + cloudFrontPrivateKey: + enableSignedUrlRedirect: false + enablePathStyleAccess: false + multiPartLimit: + multipartElementSize: + ## For artifactory.persistence.type azure-blob, azure-blob-storage-direct, cluster-azure-blob-storage, azure-blob-storage-v2-direct + azureBlob: + accountName: + accountKey: + endpoint: + containerName: + multiPartLimit: 100000000 + multipartElementSize: 50000000 + testConnection: false + ## artifactory data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClassName: "-" + ## Annotations for the Persistent Volume Claim + annotations: {} + ## Uncomment the following resources definitions or pass them from command line + ## to control the cpu and memory resources allocated by the Kubernetes cluster + resources: {} + # requests: + # memory: "1Gi" + # cpu: "500m" + # limits: + # memory: "2Gi" + # cpu: "1" + ## The following Java options are passed to the java process running Artifactory. + ## You should set them according to the resources set above + javaOpts: + # xms: "1g" + # xmx: "2g" + jmx: + enabled: false + port: 9010 + host: + ssl: false + ## When authenticate is true, accessFile and passwordFile are required + authenticate: false + accessFile: + passwordFile: + # corePoolSize: 24 + # other: "" + nodeSelector: {} + tolerations: [] + affinity: {} + ## Only used if "affinity" is empty + podAntiAffinity: + ## Valid values are "soft" or "hard"; any other value indicates no anti-affinity + type: "soft" + topologyKey: "kubernetes.io/hostname" + ssh: + enabled: false + internalPort: 1339 + externalPort: 1339 +frontend: + name: frontend + enabled: true + internalPort: 8070 + ## Extra environment variables that can be used to tune frontend to your needs. + ## Uncomment and set value as needed + extraEnvironmentVariables: + # - name: MY_ENV_VAR + # value: "" + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "1Gi" + # cpu: "1" + + ## Add lifecycle hooks for frontend container + lifecycle: {} + # postStart: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler"] + # preStop: + # exec: + # command: ["/bin/sh","-c","echo Hello from the preStop handler"] + + ## Session settings + session: + ## Time in minutes after which the frontend token will need to be refreshed + timeoutMinutes: '30' + ## The following settings are to configure the frequency of the liveness and startup probes when splitServicesToContainers set to true + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:{{ .Values.frontend.internalPort }}/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " --cert=ca.crt --key=ca.private.key` + # customCertificatesSecretName: + + ## When resetAccessCAKeys is true, Access will regenerate the CA certificate and matching private key + # resetAccessCAKeys: false + database: + maxOpenConnections: 80 + tomcat: + connector: + maxThreads: 50 + sendReasonPhrase: false + extraConfig: 'acceptCount="100"' + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl --fail --max-time {{ .Values.probes.timeoutSeconds }} http://localhost:8040/access/api/v1/system/liveness + initialDelaySeconds: {{ if semverCompare " /var/opt/jfrog/nginx/message"] + # preStop: + # exec: + # command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"] + + ## Sidecar containers for tailing Nginx logs + loggers: [] + # - access.log + # - error.log + + ## Loggers containers resources + loggersResources: {} + # requests: + # memory: "64Mi" + # cpu: "25m" + # limits: + # memory: "128Mi" + # cpu: "50m" + + ## Logs options + logs: + stderr: false + stdout: false + level: warn + ## A list of custom ports to expose on the NGINX pod. Follows the conventional Kubernetes yaml syntax for container ports. + customPorts: [] + # - containerPort: 8066 + # name: docker + + ## The nginx main conf was moved to files/nginx-main-conf.yaml. This key is commented out to keep support for the old configuration + # mainConf: | + + ## The nginx artifactory conf was moved to files/nginx-artifactory-conf.yaml. This key is commented out to keep support for the old configuration + # artifactoryConf: | + customInitContainers: "" + customSidecarContainers: "" + customVolumes: "" + customVolumeMounts: "" + customCommand: + ## allows overwriting the command for the nginx container. + ## defaults to [ 'nginx', '-g', 'daemon off;' ] + + service: + ## For minikube, set this to NodePort, elsewhere use LoadBalancer + type: LoadBalancer + ssloffload: false + ## @param service.ipFamilyPolicy Controller Service ipFamilyPolicy (optional, cloud specific) + ## This can be either SingleStack, PreferDualStack or RequireDualStack + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilyPolicy: "" + ## @param service.ipFamilies Controller Service ipFamilies (optional, cloud specific) + ## This can be either ["IPv4"], ["IPv6"], ["IPv4", "IPv6"] or ["IPv6", "IPv4"] + ## ref: https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services + ## + ipFamilies: [] + ## For supporting whitelist on the Nginx LoadBalancer service + ## Set this to a list of IP CIDR ranges + ## Example: loadBalancerSourceRanges: ['10.10.10.5/32', '10.11.10.5/32'] + ## or pass from helm command line + ## Example: helm install ... --set nginx.service.loadBalancerSourceRanges='{10.10.10.5/32,10.11.10.5/32}' + loadBalancerSourceRanges: [] + annotations: {} + ## Provide static ip address + loadBalancerIP: + ## There are two available options: "Cluster" (default) and "Local". + externalTrafficPolicy: Cluster + ## If the type is NodePort you can set a fixed port + # nodePort: 32082 + ## A list of custom ports to be exposed on nginx service. Follows the conventional Kubernetes yaml syntax for service ports. + customPorts: [] + # - port: 8066 + # targetPort: 8066 + # protocol: TCP + # name: docker + ## Renamed nginx internalPort 80,443 to 8080,8443 to support openshift + http: + enabled: true + externalPort: 80 + internalPort: 8080 + https: + enabled: true + externalPort: 443 + internalPort: 8443 + ssh: + internalPort: 1339 + externalPort: 1339 + ## DEPRECATED: The following will be removed in a future release + # externalPortHttp: 8080 + # internalPortHttp: 8080 + # externalPortHttps: 8443 + # internalPortHttps: 8443 + + ## The following settings are to configure the frequency of the liveness and readiness probes. + livenessProbe: + enabled: true + config: | + exec: + command: + - sh + - -c + - curl -s -k --fail --max-time {{ .Values.probes.timeoutSeconds }} {{ include "nginx.scheme" . }}://localhost:{{ include "nginx.port" . }}/ + initialDelaySeconds: {{ if semverCompare " + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClassName: "-" + resources: {} + # requests: + # memory: "250Mi" + # cpu: "100m" + # limits: + # memory: "250Mi" + # cpu: "500m" + nodeSelector: {} + tolerations: [] + affinity: {} +## Database configurations +## Use the wait-for-db init container. Set to false to skip +waitForDatabase: true +## Configuration values for the PostgreSQL dependency sub-chart +## ref: https://github.com/bitnami/charts/blob/master/bitnami/postgresql/README.md +postgresql: + enabled: true + image: + registry: releases-docker.jfrog.io + repository: bitnami/postgresql + tag: 15.6.0-debian-11-r16 + postgresqlUsername: artifactory + postgresqlPassword: "" + postgresqlDatabase: artifactory + postgresqlExtendedConf: + listenAddresses: "*" + maxConnections: "1500" + persistence: + enabled: true + size: 200Gi + # existingClaim: + service: + port: 5432 + primary: + nodeSelector: {} + affinity: {} + tolerations: [] + readReplicas: + nodeSelector: {} + affinity: {} + tolerations: [] + resources: {} + securityContext: + enabled: true + containerSecurityContext: + enabled: true + # requests: + # memory: "512Mi" + # cpu: "100m" + # limits: + # memory: "1Gi" + # cpu: "500m" +## If NOT using the PostgreSQL in this chart (postgresql.enabled=false), +## specify custom database details here or leave empty and Artifactory will use embedded derby +database: + ## To run Artifactory with any database other than PostgreSQL allowNonPostgresql set to true. + allowNonPostgresql: false + type: + driver: + ## If you set the url, leave host and port empty + url: + ## If you would like this chart to create the secret containing the db + ## password, use these values + user: + password: + ## If you have existing Kubernetes secrets containing db credentials, use + ## these values + secrets: {} + # user: + # name: "rds-artifactory" + # key: "db-user" + # password: + # name: "rds-artifactory" + # key: "db-password" + # url: + # name: "rds-artifactory" + # key: "db-url" +## Filebeat Sidecar container +## The provided filebeat configuration is for Artifactory logs. It assumes you have a logstash installed and configured properly. +filebeat: + enabled: false + name: artifactory-filebeat + image: + repository: "docker.elastic.co/beats/filebeat" + version: 7.16.2 + logstashUrl: "logstash:5044" + livenessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + curl --fail 127.0.0.1:5066 + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -e + filebeat test output + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + resources: {} + # requests: + # memory: "100Mi" + # cpu: "100m" + # limits: + # memory: "100Mi" + # cpu: "100m" + + filebeatYml: | + logging.level: info + path.data: {{ .Values.artifactory.persistence.mountPath }}/log/filebeat + name: artifactory-filebeat + queue.spool: + file: + permissions: 0760 + filebeat.inputs: + - type: log + enabled: true + close_eof: ${CLOSE:false} + paths: + - {{ .Values.artifactory.persistence.mountPath }}/log/*.log + fields: + service: "jfrt" + log_type: "artifactory" + output: + logstash: + hosts: ["{{ .Values.filebeat.logstashUrl }}"] +## Allows to add additional kubernetes resources +## Use --- as a separator between multiple resources +## For an example, refer - https://github.com/jfrog/log-analytics-prometheus/blob/master/helm/artifactory-values.yaml +additionalResources: "" +## Adding entries to a Pod's /etc/hosts file +## For an example, refer - https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases +hostAliases: [] +# - ip: "127.0.0.1" +# hostnames: +# - "foo.local" +# - "bar.local" +# - ip: "10.1.2.3" +# hostnames: +# - "foo.remote" +# - "bar.remote" + +## Toggling this feature is seamless and requires helm upgrade +## will enable all microservices to run in different containers in a single pod (by default it is true) +splitServicesToContainers: true +## Specify common probes parameters +probes: + timeoutSeconds: 5 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/ci/default-values.yaml new file mode 100644 index 0000000000..86355d3b3c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/ci/default-values.yaml @@ -0,0 +1,7 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. +artifactory: + databaseUpgradeReady: true + + # To Fix ct tool --reuse-values - PASSWORDS ERROR: you must provide your current passwords when upgrade the release + postgresql: + postgresqlPassword: password diff --git a/charts/jfrog/artifactory-jcr/107.90.9/logo/jcr-logo.png b/charts/jfrog/artifactory-jcr/107.90.9/logo/jcr-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e312e3219a8e0bb1831976646260683f6d447f GIT binary patch literal 77047 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
3t{2R#w6vF)uYze!GvJ=o zPSXeqAbJWooDD(Na`FrCcUSKVrd#zCc33P2ywIg`bDCIhoU1a8mG~}$$1yX!#*7th z^+|AphVeBW#uC6APUO?GOJp==T47_mvh;cESN_8Lt!%-!S3< zoZRXiLkFUVBWvV&L_9pJzE}LSP%HTf0 zGE_s^52GW~G5GDtF5Ik^%amY7ADHXQ@c zQvhAlP%M`hML0S6xTUlxUS8lA&qTgxmEbVI5i!6KVzi=QzMjqKRf4P&~-V(t~|9ofe)$_8~s;qcuHiL zX@oZ1O~7oOF1&N2!hR15EasnsKe$QE#n*gq=-7O$lg58}={1dZ-gcRYsq{h5A1k{6 zx`e4hqXVn*;DFaI(`*~vuf4IM{C#~>omZRx`uPaxc&%Blo9=FC5gL4tG#WoVxigIL zv+Ffie44kKA1ITzJD&a+I%y{^F$+AOh48$@$HpxfR*^pN=-%nZUk&0sQ;({eV?SBO zvzmqaE>9O&T%EtlZ&Op=@k+hlHqr!R1*91&*WP#sYdjkWzxiogy?>lIZ_#Ezl1NqJ zoRg^?c)&uC*t&e3#2IA34HA-fegRx;b@*s#T>{5irSuQ&5Q7zko=p}4mqi%^vR+h) z^w_nY(z+n@@gg*aNa6BAAa17jxy1uFa8MTZ7=;4ALqh8xaE1;&e)iFg^<&4`Gk#g{d!pJYc^3|9FB)iH zx~(;9YPwRGGr%eZ-Doe|&dPYE-wT2-V_g>m(Rtlyl)ErWdUTHc8~{Bw7@RGopv!Fdg}Oc{N!@fvOdn%-A{c*bLxzk@)Es1TlgG0Gd&OCsV-re38IABIYc+?2r=8E==x%T=|a-lJ|cltE`wWA^80L7mh zf3zriBSyVr;Mgw=nxf8i+5rGPSO54tGHmB3%bBm4nP;R95g)Zny^GWAf;^Ubn`-1G z>@ldmSFaN}nhdXSA)Qh9qFrkda50DTVz%C-M8~163n73)B9fk?4|&$l1kxoh2t?#P zThlKTk_n|Ps;badY20cnnB~^cJSO!tXHtpG-Xb?AA?|oOwoA~gP(F{{V$%&q*~y{S z*@Ot+Sr$&cyd6i=wvspzPW61>64YM!2uKEE(6wf+!vysuC6UA~M`}F3=SO+vf`X|Dob1~Iu1#}N^EqB&MZ5o@^yw}lPNVm2P zQ6iq-H(#){e$O4d;)-5uO;WAgE0Nr^SM~lC>*KFQlS_A0eu zJ##qLXUp*pc!+|=l^52AHAQ|lxJzY1kGcjjHQk0j*~YskOy?AUPh490xhs=zOB)K% zVCQyMcXBPPm1Y685)$a!#WsTq+T5)vdM6)>CgSkyz45x1P|fm#Hz<)6T6UDa2^(RE zC0UL}mVbaw!SbAh1PdK|8EQ5*s&q@JK5VcJ96&uOSeK#+VbB!22I?_#*1MG)@F&6W z%il2>_oA<^Tfc~`_>N;+ z=V$bT>wgY~N@Zp`)ixr9vmG5{3W~FWqrp5x&7?Zf;+lpnMEf}3@%!yKV5|3~PlClm z;VgE)E$Z7AF1qpyW`?hmF0$_-L_3x8BHKYlOg+LikUMVNqEdw524vOms)6uI>v@1w zdV~$0Mxnt}_`N~0Jx3hc8TjH8>v||%tV-nKT*nnMCyWGEW<8fHikTx%BM#(v1XQS2 zx`2+5f8mONtA1cM7}H%yF|xbDK5f=Kz-W@IRk|lq3CY)ExB@;wQON~vH3yaKR3Hu$ zViK_vhfm1Zc~QHrJJ3CEY~fzQW&~=Fx&LUc@WNpj?{I<|6*r2#@NO!tPPGFt1RFzw z-scCT-e1y#i0WJ5l)!UMg=+Mo9`r=^3(H4)$7yCyLQBQWm<96Uuuq~VLHVIbV&7mx z2)ghKh;?&Xc8Ec8){SDFErb#6G+fy`GBs z-66Cazosnxz3r4cCcl4d<*aw%JDd1W_r<(5MVyI!TsjL>$7opMnBNZ6 zoN1$>nxziKuSEx?U4O&kr5ymp*g)|xI7L2UN=6FRZ`k$g-^~;w&e|_fS&e40p!+L- zd>4L{f&P7(oTj`TH_S#j9T^cjQ_9zPgvr2fA1Ng8lNc26Al0^@^NV|v%=?Gx;iKD@ z#Ih} zVq)@)qekR9{>Q>~7I#c~6`!Mq;9pcb3E>0zFD4TiWXSKb9shXzsOoU=3`=*nP7_vA z`}5nl*T4?11J0>C0jiF6)k60Eb2m{9CLm%B%3USJBY)Bt?z=<&b4v|4(Ccoj0(+;3C$DT*Qsmh)8 z(dK99&Z)lZD;qX9?AQN(ncL6d?*r&d@LzQgdnrfQo1Kfk3adPPI+If-V6eO#R+v@# zB<=KXjDyZjXI!1-XbBUsiL(;nu?7V!>?>}!cEn=#<`=;*IE3&AR5euMGu@d^=aX~o zoj?M`Hkrzc?TGsahyBXxOz*Xv5AC1EK3i_VIpW)q_1pMcfwudK-juwhK>cn?#`SY& zqRSjc`y`b7c}KGTzG1+_Uk8In)~-v-^;IZQlo90r|XjwOA(D)QHRyF&M`Bj)Es?n2kUFozL0n%t zUH<}h{^kmgz1mb`kn7fu8@<#YhjK5!IGp1?95{mMI_kT~z2t6J0Lt9yoyo_|R>pDs zp3mYu{{AFr^S^)*ZtURN@h~5C>O{K4KGX+S)A4e{t_LS#f5RW91--y{dE>o1yOR3H z$KW8Kj?K7S_pPwg)x+fqs-)X5evYscBzUL7QL4A(Zj+r2^u4feS$aJ_7i3gP?Xy4k z<1>V=N+s4T7SlIo8_4u4snydzi5i%8O<$RUklDnwI8yrK7rR3=DA>2X=mACUyXh<3 zA7WzbAy`uPK<?|nUgSj?=*L>N3mAICT>{P=UWl%xi{ zG?^!4;TkUH_bg*$1*PogZhzUHiy;#HQhCxpyf88HAgQd3*w6dVB_qd;wc#d)hw-;3 z{ejQf*5u@*qNENgjgGX84bw-PvQ}nM(>c(P6l(psdp1Cc;iq4yhv|`hlyKC@JhS}9 zYr&kA^&g^^e-Labpe5}gbKlIw#wrOt?KQp1fp1&@P>a(}qC4_8$p{4habMnFDN=s< zUpd{?PhAK~DU)Z+F}P}Tb-PIiN=*KZ-BH;=s#EsUVU14UT2xI{eJA$eC)AN#QIqud zCxPv_pygkEeEd4)hQ}W! zIW==?SL|s9VslP#(YAQwUt59bA$=2Qx9+W(>Kgwkfv#BG;@3=4(5a}sY3NNNnVXDu-sVh{D>H1F%-vLc`EOHz2}dANS|fA* zGA)i}$tz>fy(aj;m`plx7}vFYv?vxka{T0YQe+4)G*XW0fe=HzN@MW$FFPBgrsIXQ z>79N(hY{~0Y+vT9$zx=4eyvFh#v3$s$zOqxe)C+ft^C_0kR6gpt~X*Qwf*n@_OeWXxjKX&~7^~z~bD}e|02%gwXXp-p|E45gkd{VBH(InySVXf4kOo{!# zAq3J}8A0Yz`4CH5qOw~4LW=^nV93>UIG_H;aB`pp{m3Td$$F@5+_I(fu5%6BtQZbK z6I#y;XNrI99xWH3b*K~m3JiyM1WWfB7V%Ra_58SSal*{@D=D1+eAW=1xwz8HG{Lj!woE`z#S?)YfAf(Ywau7bOF2!-P<9^lx)90v0lIjc>nHpkYj?6F6)KVeJJr8EHs?1BjaovJ5;stYYyNlf=PI4l_p;plv#z{8T@l`s7o!*sM$%8d%SFsk3IyxIbF~oF;%V1efyf(-{l`d z4I!ZCMNGs}we*JD6>F|7nYmkY#km439>U;S{8GjGV>htLr~u4K+||lv(_@|eae2Fr z3}@xLixbNBdKq9TreD%`A6>1$)Ho@KGE0jnP3F8wzW_quIyJU0|9M@3VKhu3)VfZU z4_CnH&LarlM0(J%!b@8q73H0Nu^H_E3f1uYFzgsN0D#r!(4jMmz6*#EPL_ll-)~3z zNr)f0VVZrG(DiaYc?(&?nJj#XR>{0@Xp)s`a+`3pLB9% z|E4g1B!ioG!-aivph_M*ow?Wvz+hr&W|=C}{}iJc*s`G~sb^TW65791%t_pOrzh8W z)4HQ-{%(qIdG)owFATIGJE#daF3p?yapZQ}8&}4jSkQ(<(Gs9!PfkuwM|O<*pU-mu zMhD-t!xr@v@ZBvG=#FV5?;3w5m%y{od=`UT{7D9cXAoyOplN!pioz2hZ$UVRsc+i}5I%~4!K$BrG8=Qkt zYpH8`&#FoGr=y#|r@2<^7O4HWmZyvrkSAtcL=fQWPYY$p=C4zV`k&Ao#n2aXRk_gR= zyUU*MmxMm%gVBlmFly7PA-RXzpKH-~UXpcW;*~G)|K*9o02nOqwUE(corz>pE}inH zlpGksg?dte@i1Mjb0YK{+QArdB)h z6}e8x5Gb?Hym4=DO~;RSgtU=v!q5YFE`OngKG$Fop_dF9(DMbS5I+&B4lHi64}3jU zAB})D6bN24hQ_5n?`jt_6eq3=$$f}v6=_=Z{&l^Ryp}_qcOf3T@sJfMwuFE^b=@KMdeTS&%CWzP;a*`sQi33r{PICrt|YBD6Yj5Lth+QOXlt{(qGK(fk)dWeGw!% zvoF5oELonez3W;|DJ|{fN$VA+ldpMn52B5sJ*wAKFUv`zV0Gps@Az9!0|LJX9iM|B zyp14?`95#JQm}z>Py&+ZTtOm5mSX9JH!?h{nk@a zf@>tkyxt7U7Eq!H*^w=K>h_ESezA}xRf=rClw1cObYE-;GAAuNjy!F&#c3=gU6koC zN9FwU(e++)dB$Y9m{w2)A&X_p>)S=SeVe2M_j(51UHU`(K_JkLS3TkFEo(grA9pjePUdCnhwM7pzr?DCOdG8`?L@SEJ`C#lT1@(!=5 zukuQsk2A4!)C~Z}1`Fp3_;}A8Yn!928&MtCt7|405*8PPlGLkHYMU?PZWgWwbhS{w z_%ceHl`|orxy&YdU!t@el&xLILH+{N-cb-Ytl*@t z;RIXM2`^%NL)%b}nV+rof#bI{l3V0Uxdg{c!P#4>RduCOZJ$-lFdOm%r>AA#c)4Mn^1TK3wwkxQf;pf znaAY=!i{36o5xLfUA@g3w>u%;J|GuXe)c_f>b+8-4DX7%6VfN;&tS>v1AyRcuDz2V zfVc8zXk1G*(oP}NEJMR@mNi$>*f9lrk{g2wp1fq7NK@`w47tJh6rVdO1W01 zlw34VWEf1=4)8Qa%tdgaRLgt8e$w%wvq`f*dqX|Xx&q}*g1 za`@9HPeW);{O#}qz^HBcHc_R6y7FPG`mULBlKB6GUO^GF+pJUJ`e!U^?wn-@;xXtr zGiW(WHH%y@^3BIL%!>sQUv&vg!^Z5oYf{Ggk`CUj<+cRq1MuIg$JeGzj*7b9Dd-T; z>K}cDq(=eH`NA6xTkNR3CCy2bIZ{5S>Xu-ZyNr)tT6*0<-|&&b9P^U~^70?@PAM@v zC{8I=JhdOWAq7TgrK0*C+P}*4pnBxj$Hl8rnl>d*fa_N0l2TT^2yx|ZK;cx=K8nig z{=(fO4>G&>%_7Bc<=c3RoV#|F>?>8YxB-+X5_nEQ`EpCfXpGch_lT4psGfV+K_5Js znTB28Gp*4Hot;-L%h>fKa|aZpx??JW-}R!g?O^N{&Nq3J6bRJ4&p*&E+pafoB`jkS z&&CRZ6+wmjRMFa35h>(uBLYD{0D64WO;xiSZR|AkfHz3V^U$5#l{A$z6c?&|a)R?&u1g14U5nuI?BFJ3haj%O} zjYYeC)A)d8NrPWA(mN4lWC%v9u0l4necE3Qzx_UnVVLD6O=D)h1eAS=A+JINm@Azb z)#n!&aEWcsgP=rnH`QFm1S5imEHqDAZC!A$={Qb=h)QN8eJVGje>;l`%5t9!XDHyH zVvb+a(l{vjm117@bR-l?f62B~!D`h;w>+Nzh6mTFKp@i*Za!F}ljABYmbeeQ5m5Sj z*(U8kK>n-Xdi#|_oqhh#>_#ZHq3kVmcZyO#3YK=D9XO@ec~{q2w0|>U^hLwWVQ~wr z!1)KBV$_DOO_uTrGSRM5$%Ix{8E5&dtF>72$BdrP)*SA{1usr_(-0;U&9YuhBS5rk z-~p96wfIU`kcF~uyFWZ&)FIE1vj~YJr!ZgyAl{AqtK?uBb%>Se;(pc}vJL8`Ok2`z z4+TtNP=*}ic|d+*;5d<-imuU_5H;18%iFM~Hka|MSQB0LeP`JxKqbRK>3&XoXosVs z>GlB-?fr&zd!n#9!OsYc`nkChWE7#|t`3k4%$L$ zP(8@)&1Ant`>gxBArpG0$EcDOmhlr5^fGm8_LzYq&5J<1+ibjr+x3PQZxiY2Fg}ro zF9-}8;6)QBIpGUpQt|Cd#>OS1FR6e9zd;11H|)mp28GjlfWQmKKQ8=(l*zUZz%hl) zQAq7{HYsLr!!zDp5Q>_Ydg12j(QqM$^Zn6v_F@MTQB}SaR@X& zoYT{sAA@*ycim;tUIg!(h14o9XN)0AEru$ z)J*=+C=aT(Uv|*6Uc*WhQ4$GN8vihZil!iEfPf;!+*ypuXqqT<+=p|OY!CAAla7CF zFpbQ>Nl~(c6sR(B*Y@)~i72hgI5l||3+8XxS2@pbhLE_vuqQUP;Fr`smg|01AK3HK z6r02Ms#v-KHORIM1PAMcEQiG2fbu&*anaV%#%!$L3(yGlCIr%ROo}=j)B3g^fxNz0 z-i`@^@+78jISxQ0LF?7p?Ay{@IEmZ#Q1v?M=pSo`r*yegBDZPdD7CE_7giT{k3OUK zt*!!CjP)jVpeVj_k9D6rfONx;X!r&nespqLGMF0apE);U61*t)R4Se^AM=lim*B=$a`BoI7a`OV$atjVZIu$Bi z-GOH_Y9l%ZDR9#0KXXRC*s zc8)`y)6OmU@>DR66{$;(+&`x^e}+WJNr?zn+HE{8FKs4X;Js*#h@1DaCsnn8)DF#4LlUZ1s&L{MbAsab?b7xd3;3#2+COqdO>E z)g%3|75am#?4NaU12~}~1B+VWGRrNW;E;oA3Feg@xA_xjo&VIV;lV(7e?I|x&%( zF7wyFW99uCwK8mlIN!c)X=-s>3H7ew_w2@1E3yo7yP!eh&Fs_KBOo(FbPjGE^`OUF zZbBV2)LPHayJ`)+-$c4w@FZ_#8L~DphSN7#gH>aczoP ziOpyplUv>lH7NPK{>$A^7|&+6hN4eG;%tfRYYYSV4it@e3(?Kcp4>A&;HL`Ok!hUk z%}bgJ=YB6nc6mVu1Xg-Ox(E58u2-5kB|0xtq0@%1E}al!#WiH^pt0X?p;Up35npKm zrsj^mCmEBm5X&?I2W{&`Z%ssMX9_4xLdncPsfG_}#7}$J;rbCHd|k8vrsSYB!CP|2 z4BbFtz6JZTRb#;04+!8`OiD2^k3Jo%$2`n!IoH=BCUvWrp7)hBCtDl1(g6)Ob4a!k z)q1^d$B4$;uNr@CMw#W`*frf+u+j>C=4e z!@}U7&LXtkBpi3JG@dN9!@?HfWjLQ!YlLpb4AbGr_Vs31YTHl&j@!P$`<>F|E?hvj znk-hP_4;0<5h6u_i^2V5RVr%OkfNd2P{3IJY%wj#K;P4Lg(?p!xyRt_`7H5R4w%6R zUQ~VAzZ{ruBDOSI>-)y7YuHR+z5K!3{}b-J$2e~;1W(_*uCF} zO}Dc-P@R>jS-5M^39HB3O@9HpCk+U9U)K=#5(K7eyrnwj?sH~s*pHPZ1%PT$JW=LM znR2RA-6Vm^%}Y&3uG7PjGqEEzO_4E72NC==x>Bm)O~!|0?%aI2zHoE^qR#pH2y@iT zAR3%%`)*-pWqdyP%=kA1)1|@`st&;({5r_Sq+8F3BF;I}3WwY@oW?aq5P!~`4~ zd~)@nLYIC0k`0~0OTzl4yIg+z81N5#duF9hQEUkxs+m`N=v-@s%lHwuCC7Z6fV-8OX{G!Q$ zTq@dnOMTCmy>!1-2i(fnmyr^(lDGae$5n$BB0mHlbdSC6D7hq8(#&|#@ClcFcf*t6 zBO95VOD5|n*heCzFyj$QzA603_{Ub~i&mRAGrj8z6cy}f0p0^b>cB*1sN#h8+)v2A zSMRA#m381bdj@s*kiMd;j@HCTNdo=g*!62rYK1eV`fB!MN%sXYXFN_pvzO>H;UD{+ z{|V+4(GuWN(lPuNH<=sxM8?N&U9~Cgs~1YL#+x>U5bAu_H#FbG4itQ3a%b3_>WM{inTi2!<%$omPI9E2A~7A!7&Mc#)C=Fx4;Mq~5r3$Fmod z2~pni{}kVm_pUKitqU0O2qP{n;C8qcAt~X{Z(LJMj5ys^c6I zIQ1Zjz}AZHy)Lpf#qQ~o24f7t?Gx9E$SDu2=&h6w3j3{Od!M{c)@39cUu-92({T`) z5*UCQTsRFqM{Zm-d1lA(?E4$v7W{Q$M1m*~}KF1CP~m&`OfeEq!3$?Ucsv z(%i+oP0Y_U=J}uy$s6?r$re1_o5965 zJbdWzF%I#-&!L^0bH2{7&kx$uB$#~ExuSq(!f_ThC&5lkYctnzj&Db9zM?rfZD3QA zYu2hBG!mx#IX4zPHm$8!Q57U-Kx6adq)J4`xOe@SZu^v-^B!z9uhBhUJADsOkp(gw zsjp%PI~p{gH1vv6AAjd<;olFXeRO<-`~=~t$#O^agQbm%iTq%FWDaFVZI-2C}G5?9|B zQsHC%_-kzB3qs+RL+r8hMdvsN?_JyU)-aPtED($u`o5LIf6$leM5RP zOlEiU)raK!b|;OFzhXJz$~P0 zCVpgBJ1Ow|5xD+Sve0InDY+24Q~$!b@UqVJPJ6_K7863vTHZAYY=sCFZjSnv|8j0> zU$3Q@yTRf{avk0I4~x%+G0b&o4!Gk_JJMIcjEst*(Z8H2Y4?$H ziZHLU6|~dUzS)Twd_W&+v*e&Pzq0W8jaX=7GM$6bAU)gEXeD->HU$T%DdqeQ@@*VR zcEm{&FWrzbnguFHhobnf7tJ@;mfQz^KgHpaBrNW+*X47=yg17mX6?{Tn=G(^y(*|8 z)hPPDqa5dObMKy|3$yF|hDARGX2N7rTpAMo)IwBXQH>@&DJ}SB6Eoeeo*7Ul@D@2~m+y52B=^ zfJlvjs3@S6NU4ByNeqpnpkjbQNMq0f0@5j1v_Z(w2-4ks*M0{*o^$5Q{c=Cthi?qC z=Y7{+d&O_X-dj<2I)SHYCV4ijm0)N8?mO{5av!$L7qSUo+*IZy?TvP~xm$NTT=7q5c^z8_vQ^G+c!~jlFzm> zaX-w*&lI@M)VaRk zOM`d2OL&$0^1eBuj?ak;mp)9{w9k)ehU>GkQX4?eIZGbaa(W(|coa!ue7CYDF?~Kj zN3!Ed0CP6Z^XHPuU@}S4SIvRp;C?FVRHz=C)k!}|uAHoiPvYjjSw8kj4Rtirxep^|#l9!43X#m^IKog!4#rpu$0 z!fb>`$cbcbX5i!Q2de)g#?zTgWlfpwlGWFFlvz~&(cR>0Kznnuj#}el#qBjjDQYne z?fISot1h>lYC4RLhCv&Ie2=NKA`>z41(ecP48?V(%->ym!bXDj(%&DT847jm3Jo}w zKKNG#;J+eS*6%h3<=@7+Xsd6RF0sTH)oT=tC{3^Rz-lQlPv~T7P?y^jd$%Z~B*b$2 z;&9y-9=L{eHaJ`GNkVFO5VX;k;s#_$W8?2-bDsD9;0cweoe7*QJmsJvY6JWL+l%U_ zn?=Fhezql!c;8y9FP!pq&+)dQQySGR?N4l^Wo7anuOTioe{aYTq_%bospjwW)N~9A z6rJ3f);qXtR@jP?Fe~S(PiXp6Z{z#yY+gUeoV3R^|D5V9;cSj`osT!Jxp#x&b^K|n zMqhJx_x5Qws-#eUxt_^47B5st@|9#uQ-iYXyeC25inh{h*wP$`f0;_ruomlt5CHsT`v*OV|BRBFvzlW16_6q zZfOaij5JMp5&dznbDD)7hBhvFH!x#dG!HgtxMV~NEUq56D_>s&%}t!P%N_|@ZP~uG zV|97gX5PJRY5j5^>^3XS&%w~Swl^VR4CO4eN{X%5jE}KSE7+0)gH51Ln^JIc*NgOM ziYtqCb>|F%?lG#ZC%TsiJKN75aPb{~~0LSx-g z2S-D4itPZ@q^qv~h=9D498p|@LC8V$`3U!CumIg`OP9XAP0!DI;smMeZGxX2#zq?Z zb?LcoUTfjk$kolV z+;3LWjuW7Vs&%5%O>v6v_yXwHZS?eDGQnE(g#H^($;eh zn_IoJG+qG~$nD6n>tCcU+jOBNr;W5J;*I(aM(QMvQLFYJ2XcLf_82W|>;=bG{Nmw9 zD^BO>mkD8&w4qE}@;Xz5BJ8Su(rVBmyYYu$O3<3iOjK3sFO}qY740RKhs;SyOc(Iv z8by@I7^+ZFchDhy3t^QVlpQ;uc!gS#PFrk3-{a!EX`asKlNdhW<^Zw|=ZmONY0|&p zY@AH=*3c|sbSx|q|DZ>jsA9jZw7tT)RbxBRlO|v&Xuj2U2=`@n-dDR&Sex6G%W16c z5#4YdLYhFDGqkMNhvc-tQ5$KSj3u26K+wICb^Vqv6BBOf%zX1|)ynmlZ5TMKt8g7J zjPtaXYN{Bm^44&lxjrfGMX!Bep@Xq%s-vnqk&G5Pz%adIH=d&&W9sQSJUg+l9;a8b zXW5RbJzPa!EWf$MNgz32SCslZ6D?uf_3GPWJGRR<+w=`9Y|b*NU~7@i5{lv=X_KFY zZnLnvwvLJ0`UG)kI!8}y)V#EOOL~N6TVFd{Vb;tS(fNRI8`&{X=Ngr^qS1U0&E=30 z>CppR;Y;ZTL~A2&YY%VdA%_-ncSa`^BFj?}Om{vkD0+>v%4tw8Er;u9-2t&46?&bV zFS@v>LqpBS_qn+_-MYSlx7%H^X)u9TKB}IDe`Mr-@@+{4RXbTRL9*TB0pk2~E<;;g zp3Ir#d!!ks!x*7^q`Ep!u28vx`9KfU6r*rt+u+HV(>jK z3>x67B+;bO5$bS=9fdJ#h*v^atxu!%um#GeuA$H z^Ct`TF4F6D?pc0C4Z2MD6s+OE(X2c5?1eLx(GFyk?hpRp3~>t-jtH-9h#p~^wvh2y z_!*t{y*iJ4!3?A0u=M`We4sO)-gC0ux7#gdq}FH5jB%!_W_epdd!n`3Z2WM?Px(k2 zD*lUXd9QmOnaM9`;ch=YdwMf17hX4;aPG&H_Ac^VRl)wmIaKA*vG6b!jEn&oRnQ%Y z&A&>t9?#;c%5ei%ILIM?@31K}C~lxim=>bb>wp~q!Ab&NR)?%RGzeVHHn%t`WHINl zmMR7?W|-oKRyguOg81+y;KC#A_qe+11*~gd({n}G629LfDRL$?RaqRReop&}lsi+~ zrPM0*Q`S_*$NVIB=*dNm2YWsy96`95TM3pn+)a&@s7+I>aj5`8U-cu@RdU=-)q7lJ zEFofTx17l4+}pQv#GUxCV9~Te>7`ZowU*a|dx_v+L3}2c^2^ES8MF#QoG{ivHfHY6 zj>LeBM1o@8*SmL$vgx&3YP#FEX8MpOV%!E3AGD@bLY<%HaL(a?!Jy@`)ELWc<6gG1 z7xLB-M~EGJwdCvUc_+yoK9Ojzfwzvv0l@0T^_Rn+x+KeOB06J+qTOkuhxU9~^i4Ar z*~1lu*2Iyp{)cdp%JLBxAW9QcKPLb(qcKiY?dFnVY(0XV9~!2-+|}Oz&je3 zJQOX=dllzP!$h3JHHl0;))R1YZBGhn-;|$1F#EA7Rt*IcpC^+Uf@Qd+p1drO_ayAH zuI@C`@*Rw~Z+)}U;5CA>>mF?*(4PO5>1n2IpJ<72#tAsofl7~K%G;WE%w~A0x?oD$ zgoEv>)*bu|srPOjO`*={g7`Rg+T$+DGkT-Yj|I*OfC%*8!rDZ(dqm-(> zan35yNy2q1&qy*_V?}nPVTC5|l-`33c`ClKbeC-KV&Y>q1ykSM*5EqY<2G6)yI3qP z`K!t7sSrFsEaBa#*eW*VCFkoYd{(}(5vQ7p&&q6T3MdWYkS`<0wQA7JsRqqtfWfkH zb_7CxO5gjDh#>`C(KzYUqWTl=KYM2xJgpKfWm9Lhr!O@!&pgD>^mKidwr#yIIX)jf zLAZ9$)s9W33Gy$+&Hs~qqcBI0nH_{XuVfwOc7~69JNMbnJGOr1bDjJiJ657k?IE(9 z`$BBVru-R>Rbu;@U2?=192VmQMTSh5T#r5+U@x0$ZI{ygf-$dbK@|XMT(cMUX^YYn2lgC0E_wA^W4o>u;Y^QKSaa=})0?xLD%k3W z^mHqaeRxHVYZZY9p*eBT6ThT%Y35d@Yg9rfkD;hpp?y8gSKRqOS10@y)Ukbub0<&5Bt_HGLGEBu^h5?waO}QjHq9n z8wv2n`<^O{f;yQnOIS4mkxgNvEu=649<3lDU$t(pi>eb&uM|EoGn1R*tYVdrnEK z2&yAA5}IjiS|%T!O}42MK!5LK#eF))L;MiKMQ7IKxjLytTw%M2Q&Me<-mm9qP!|TV z=GB2*4?3V;D&{&;RwB%o4LGt%tICtJ(gEV?{Q6{y?tP| z@|~YL+LA@EtpwQ55HiC~MJ>W|A3Jr%lzzC6))w1!x5+{s z%Kt}D>SKL$v+7um!r&~Or<8HqOLwJ;{AqE1_id@!Jh|PTN+uF5W}9hSX<8gFRSs}G z32t%v;cuw4Xgrl!ATy;fJ1EA-r^ziL5xjR)T5dah3l-FDFRA#cTwh2U=@d(ZR^+9!MUdO68RFTdT_ z?Xp{s{k)jCrKFDSqFV};`2D%2=Q?T5*0D}l4-f>1{UA5GykY=5{aI;S<>~cW`5i7t zTT|I@>H!nY=+k2k6gn`moBH(T znYR^ISEn@V%a)WcUm=4XUU+sm9FDb?ssZP@p1Vb+{qAE8V^dUPB?{0$zH3^x-=lF+ zmyi)?jddfgAyNYbp%bh0qhpLsz|UAV)8BJ{+O_Qrq{F}fMB``d`sNn-`<>G#R{Co3 z9H3wJ_z6g77;x`%YrIf>O29TH9%W_>c8?6)IP&o@NhIqaKY80$C!7LNo*cus6CZg3 z=~s!{D-o^xd$DT=%Qn$Yn{H4NjvM7OCg-M5G7paRuri0V_*eM$GaZ9h9;6q$UNxv< zrDi8+M$iZgDs# zH@C{wew>_HypAt{*Wj#(0I-$ihyYJPm-b}mPYz!VmEzd8Q8O1J; zZP)>+-rFY^(1VwDzrNb~6%t0l6Jr&f2&Z#vp#1;hh z(@>TcMAZq#j}0aF0XirwO7Y16Ftm{RjH~>T99F_$JbAS3=YG8n%N(&wfd_}No6ev) z0TNB`C&i#pm`y9tcwo8CMo;m|=+(!Fo8&~T?!~!xUwksOlOp;MS#XgayyvbZ+My4r z`;p2>jex~N0uR6HGzUqbRZs;xw$XmFfT#OfT&f^>U6f(#oOTrHvIv|G!~5EK__5Xf z!Z&3JhitL(Nhiwk${JJ(gGualAx2+qluHuYu=N!V25`K=$|AmqTP0n_2|Mu%tNdM!AFvN+u9hv49vwcc5SX z$%LOPUDD|2qlBQUTQKXHNc@FE58S4O>mSUh^7TuPV{HR{v?%s<8+4IzG#SO92#C`z znAC|%RnX*G#zT&-tq15ik9cY(rH_FO8k*uvWsiax`P+)y@7N6P$1Vs2ZQKvj ze!uO?VMz1xe{QjvAn<$CK@tTgM1K}*FxpAmB-=GNIj`~4B`sv_~=9RU418ZqAS#D0p`04m1$gOC7 zbjv>6f9wFlu0&lX6u%}5xWo^3wx1EYR7Mt0>MA&Hfw0k04C+Tk611TMhxdbMC2w=M zEG)Q?hY93C?Y3vVnRzbO%@W%D=9tw2%Lp#0JU;9$#1<07W_hjfV1h>p+g&mqnqmWWH2dX_dnaz9JQa$!>T0^Py%>fiNax!V9>lGH z{|D&kne(mRk&Iq1@~-*;nHChO$V}Hp60j#jt9x~p{STrj*P(>@Uz>i~7J>8AW;4=A zXm(hS%oM!6$3@Rje=qy`LGm>;yREz5@dKzj=iPt%6hxM(yrihSqEW0#B?GuSpu@4P=h z&qT69j}0jbV@?juc5~ep6=vnOH=F`T|hY=|F_ zSwB;e+Q{Rpr_rqN+~{Z@-9=<6kz1ZOE^mA9OZUUMVB<8{Asoxh&zbTMR-B}U=Gjc! zn3Z-jD32i6z-EP0N>^t#))TsT0=9U@(tq$GcE0K@~dO$TO4)(nUAj#$jtmtpU^ zjz0$i|D2R~0iMkIy}o6RbhkTctNZ9y^_SR%t6sDlf@P3D_0nY^cTAuHGUOJO-ERVi zxt~L1UE-lT-4FG{q=)_)==cOkpOoKnAAqpwRdbW`CV&>9uPk-uDdOZX6Zs? z4*Mu?vG61DN5R5UZ5-ry@Z9>uk%gWJp@kN3MQO4n{1|ot-etkuI&dP=uJms zeF`c{-z75(=_vsH5%m&UqATnm0XyEz8%@E-*n35i>(!+*FDZj09?-hY55*fQ8kB5? zEF)*XaYA|T7jgptaJ&y~o!S+h362v_qG~6`x0mb!D{W5fci>^>en>2uHgea8bRwAS z7&jp?7qSuH)`O)k$X7$BPMR{&R$FhGAJQuj9gW{m*LfN=h?;12Osq?t`dX~VRpJ%S)lc*TZa9I>F`)%jgP^`@@_lP%5&3=K*T6DNZc7vg*N4AM)y}Vfk5KLIytEE_QeGcc za0c(o%}Q+K)!uKn*xxQW>=h>I|7$s*@0UNe0Xy<#J8>5;!voUHM@UBmmUXmB(J>lF z;%SE>wDuWywaD2hDxV;ypg{fLgqeE0>oKG+wnXTdaIv5LhGcD>#tbARpvz!dDgSck zhaEezsuxsaSKtoVLU1@9PoQxdIP+POibIFjXz=Mw&j3;5pj%$5&2{fh+>B=dSqGwO zJxUGOmW?L;nq?noQhX!(0f3 z8-GboH%LynmJ<*9T!S?efFn+j+&z(~h`+QOwE1z7#%;F_NSjr)O5~^IrpXvF4*%L6 zc@j!Kf#&^ZPy=EwJ*ui^@`o2f+Q`LZao7SlS`R$u`G+D#`+C6u6~$vU|hCr_wXS%O&1s{FDP03lS7(_Ckhl~T*$u?zBz@?0spSR){s_Med;t*RCz zSu2m#FIz&*sGZ)%ND;Tdzm}?7$h!HC5}3o!uQ-^YK}BP}>5{`Qp2dUK#kcGj$0C$( zbZ^3>L;xQ%GCIEN%a#{xAr`Ht9tv_D9M-_;<`Yh3Y5eH*IpRUiQp4-5)q(o7j26c~ZGG}z%Be+=a)sWLqn}bC^|n0fI7Z&zgCxZnWVckDZIZS7LUt&C7P2O|j~MjND_E`G`3?yRZWxWc@(RF6R7 z9=*k37x3aX8=Ev9cK#L-5lmP{8Mu=(&nq6IOvf_Rd`mta4IB(2JRf(w$sk+uOYq77 zfBH9j)WYXTb7JCc@mnW{K%-h9&gS72HLX4q3C$?2Va`9;6LhsxU8+W_rc!;cq2ZX^ z@}X_u$_LJPAtBzj=kwPLL#PmSv$=Z|eP6B+@H?kSM+G)wVJS}Bvztb^MLHKi={g1; zWP!+zB@_-PB^=&LUePWbO3XTy-vQ{HYxcD^5Vv@NZimk{6_y^^v2Un?1}1-*J>P!2 z;Pf3eEDA<=U=u9j&Ick`x3KX;6p~x3B+4ewPyVnSvXRVYNsABtxxK)`84415no~8M ziMt5f8@@*B7r*|^M^RWvQ_nQ)3}?T+uhT3V0VcSo5AZ_jj?20sPkbue1#sup%lyIyPMcsooz;6v>!OFA*QEX zx&7m-uS~RhqHdta$%&2*Q$DAlyMrVN|m|G;EpO=B`lz;(u+g9?3Ytw`P)UH)3%^VB0Sq| z!Ae7lYv{%DWQv}tYMFl1X*y5WsoZat3teAhP>&a+w|og1@-2d7iNfeXT|O^5A7Gy6 zJ>44ur%QKH{3)$lsLhNk#H}7ARXfh{lY#Rwgrzoy)(AH>4T7vfLNG4M!pvqqpWLZ{ zL}Uqix#vSXSCHoyK*P_(wqM`~ciws<`(t;L8w5zlOuw;TemH%97o*Jh3^ZL8KRHvv zv5svpIDOt&VZyEKIwA@ao>~YC)iTAek~hiVpzfV+UY)mpxPYUe63o>+LV1PlZ0}D! zy8qRd2%xrQCG%Qvl+GND*e;9~6NXdoZt^6KvsWSjwu_3!Wt#LyD)1a82}N(Zct z<5G#1neI}n)+4_O>-~q2bemW6)%gGulRi zs*u<A3!la7#8;6{M9;a0QSkEkdmPo>PW&4*3Fr5Bv<<0hw~;8C$~)P7Pyc*_ICUs_eUHn7kT zEd*o=Jx$j?I&;@-W=k_f0bWKl0LQsI?KBt)vmU+^lnOdnGmp%I{{CXZ$yRM?$DX-* zUdyc+5M7PDyN|DFXx8)(N9{gje&g$a`d5t!*wx#p{0<4sV;j?-+h0D}e^kW2Pa)YV zX-V!*0u++0X$Oj@_aD1u-&YSlc3XIpmqciAMgEgy`fn~E_m|__>yraznXi4=Vdg$t z&v(l!q~FU(&+{wFOVYM+k>geL-d}Bk7!99}wSD$px1$|b2x)M}v0RnH-4yg5w<~?ZoU5otUzhAQ= zPJcq<-!g{%5ecWSYNsS>klS2;SuXT5ycB#RWwz@Vy8b1a{{YO`!u)y(gzS(8zGr0d z*9v3*{+4(JjQRTvUDT}m=SyJ3C;+h0R}WS;#Q*WF@+(;V?{)nDIzs|RaEF6E3QvFZ zpJ!3u1-)TyboTmfgMUBo9}NGK1bhmt{uQlc>;DpsHUc}=ognuR{Ym*AfM6Qt{jB8l z+Mi@FqKka1N#xcQvKv?-VRdm<7w3;q^Kae2Ou(wK?2uhZr&IN)G`nR81Ih+msMP8m;?v0|j{F{Y^3oTxyK<++SJ)s>i>KRcTiNa_hvNUReP^rf9mg!0`fCVnNJ!GnnfEzyHZ&n;b?6g z1vVIun#Geh&=5|)&!NDEi;KZ#r(l+kbHY>;DY4<=Qn2|E7;qyvOg)YQ8{X6vUe)q} zS%&KJs+&+?!@JEz(OPhhLDEz881+~RY)EH)JsSYK>G-3*mDXxfU_&;~YdSVG;N_QI z|6U4gu)>*tFogb)vL4$TYLwVuy~(^x4hGycw!JGyi4EE`A+~f#8gfDn9#CR~_Wwfc z)*@*L4!!D4fei=i-(C$XgnH64C-23}6xeW_tvEU?9|p{Cj=2~~fep3(ubsMK^H+I( zNok!11vWH9z4kUj1M=SX26R(k13}RIC%N<9s<^h6f;2epH}BJgW|CA(tFrgm6r@2H z7vfR{vkc7*v9qAW2HlMzt{-5){g4m`6AEm2Q(tm50^Mxh+Ok_$lL8ywvAv6qKsRqS z)E@2l1!*Q4YaxOt>PtvhP{5MwJ2y_Y6~ZilB2K zzSl^dra%JhV^^1R2ND*XgHOp(B0+Nt|1L=&!9lPxikkunLj3ynouK*Ui7y&zR49-T z5|wgP1e)Uv>pr`d+5DFaooQm~r*wgYyt$xrqLfIuxSgMeA4pIitccq}fds__{q#=g zkSLD}(X2gBfrNyvlq({z4b-rvBJVUM64GVV&&mJ^uFgT4Jd{Y#;^yZ?N1cf!RwOb~ zAiVB;@j>-VlLZ35H)kEtFYP;6mmJ^>ds^7UrH_W~D@e_Fw#a z;ZP>+%@!3Yv=m72NiY!Yg3H;H*dxnb&a5p=uJ=XX*Hlt{={R9Bk=5++=p zn$S=pL1zy?zY~x!CRmZah5`vm{D$^Xp!W*-Hyf%7GgJ~2|CCp#uxBV3dhwtEdBj=8 zR>ObY08_L3;qXOGLle;ZuS0qt(vk;N&G`Q*^AAU_XqudY0go;9s-6%i@GgxX z!Y?;)vdK)cmP9_g>9<=o$k$faE`|Y~A%W91I%5B#(1OY-IZ!PSPNf!VZZVV@Cn=_* zvsV7B6a)!B^$&zny--74my8VsHVpA!os2@apQYP2ROeD)!<52Rn%!u?8=G0BMha|L z4%YNC0Zj09Kx=JU(tp{YT=ZPi`xFd#Y_3K3#Noee@Oy2lu74M1$&#!7E|dZrSW?W@ z4Q{{y>Jar0eiYbX_$ov#2(=yLhqH?Opuh$%fl&V-beThTfWOFP3T)WRZ)6`0mO&v= z|9wR!1vczgF!I`s2E4l5b*BEmY*4NuX-1gSm{rG~}q&Xj5WCx~sbRQ6vq)YIUlV*pL{Z76PC_C`^}C>>DLE2uAvckPkod z7duCR4JQ1?lhI%r6rxn#SHj(W|4|AX1!EdsG~ngAuCw1Lu%Tt!#rGzF3BLBI5Yt{! zU_;x^i>0Svz++<-?R8p$mFm{H@N#F;k);=dn7W76zydE2oSnP?*KQ`HStWZ~=r`-jOcF zY6^T9x_Gf`WseO_brJrEw8DReGoVq$DV;MQMUbx&|C~uF*C0l>JvX)NkF! z?**%r6qpEd`ypU8(ATbnp+e|KvAcIKasLv!+}UH<=qykJ(84S`?&x%!s>@YL4Od>K!Z}y!SmQXrvEg3YG{X3w&c&19e{t3_Ykff7J71m z?_VYzMnocO%t!dx^vBCr-bZ$bC$S4Oey;-dhwADgAt78{Buo`oH3>5ztD1zF5d5mS z!_4%mPr{tjs!zfY#42$3{~tJvER$07x2;*T#`E%$j{Z#n`!!wv##?u2v)Xqai~lbg z9T8}|<)SP9uWNe|+`Ds~T}b5Rw*R8kYAVaDZhM3OV&2u4tg`E$G197Zt?J1i(cr3C f`MkUvo-M@p#hFOE>-xWCTUi literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-jcr/107.90.9/questions.yml b/charts/jfrog/artifactory-jcr/107.90.9/questions.yml new file mode 100644 index 0000000000..9cde428709 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/questions.yml @@ -0,0 +1,271 @@ +questions: +# Advance Settings +- variable: artifactory.artifactory.masterKey + default: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + description: "Artifactory master key. For security reasons, we strongly recommend you generate your own master key using this command: 'openssl rand -hex 32'" + type: string + label: Artifactory master key + group: "Security Settings" + +# Container Images +- variable: defaultImage + default: true + description: "Use default Docker image" + label: Use Default Image + type: boolean + show_subquestion_if: false + group: "Container Images" + subquestions: + - variable: artifactory.artifactory.image.repository + default: "docker.bintray.io/jfrog/artifactory-jcr" + description: "JFrog Container Registry image name" + type: string + label: JFrog Container Registry Image Name + - variable: artifactory.artifactory.image.version + default: "7.6.3" + description: "JFrog Container Registry image tag" + type: string + label: JFrog Container Registry Image Tag + - variable: artifactory.imagePullSecrets + description: "Image Pull Secret" + type: string + label: Image Pull Secret + +# Services and LoadBalancing Settings +- variable: artifactory.ingress.enabled + default: false + description: "Expose app using Layer 7 Load Balancer - ingress" + type: boolean + label: Expose app using Layer 7 Load Balancer + show_subquestion_if: true + group: "Services and Load Balancing" + required: true + subquestions: + - variable: artifactory.ingress.hosts[0] + default: "xip.io" + description: "Hostname to your artifactory installation" + type: hostname + required: true + label: Hostname + +# Nginx Settings +- variable: artifactory.nginx.enabled + default: true + description: "Enable nginx server" + type: boolean + label: Enable Nginx Server + group: "Services and Load Balancing" + required: true + show_if: "artifactory.ingress.enabled=false" +- variable: artifactory.nginx.service.type + default: "LoadBalancer" + description: "Nginx service type" + type: enum + required: true + label: Nginx Service Type + show_if: "artifactory.nginx.enabled=true&&artifactory.ingress.enabled=false" + group: "Services and Load Balancing" + options: + - "ClusterIP" + - "NodePort" + - "LoadBalancer" +- variable: artifactory.nginx.service.loadBalancerIP + default: "" + description: "Provide Static IP to configure with Nginx" + type: string + label: Config Nginx LoadBalancer IP + show_if: "artifactory.nginx.enabled=true&&artifactory.nginx.service.type=LoadBalancer&&artifactory.ingress.enabled=false" + group: "Services and Load Balancing" +- variable: artifactory.nginx.tlsSecretName + default: "" + description: "Provide SSL Secret name to configure with Nginx" + type: string + label: Config Nginx SSL Secret + show_if: "artifactory.nginx.enabled=true&&artifactory.ingress.enabled=false" + group: "Services and Load Balancing" +- variable: artifactory.nginx.customArtifactoryConfigMap + default: "" + description: "Provide configMap name to configure Nginx with custom `artifactory.conf`" + type: string + label: ConfigMap for Nginx Artifactory Config + show_if: "artifactory.nginx.enabled=true&&artifactory.ingress.enabled=false" + group: "Services and Load Balancing" + +# Database Settings +- variable: artifactory.postgresql.enabled + default: true + description: "Enable PostgreSQL" + type: boolean + required: true + label: Enable PostgreSQL + group: "Database Settings" + show_subquestion_if: true + subquestions: + - variable: artifactory.postgresql.postgresqlPassword + default: "" + description: "PostgreSQL password" + type: password + required: true + label: PostgreSQL Password + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.persistence.size + default: 20Gi + description: "PostgreSQL persistent volume size" + type: string + label: PostgreSQL Persistent Volume Size + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.persistence.storageClass + default: "" + description: "If undefined or null, uses the default StorageClass. Default to null" + type: storageclass + label: Default StorageClass for PostgreSQL + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.resources.requests.cpu + default: "200m" + description: "PostgreSQL initial cpu request" + type: string + label: PostgreSQL Initial CPU Request + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.resources.requests.memory + default: "500Mi" + description: "PostgreSQL initial memory request" + type: string + label: PostgreSQL Initial Memory Request + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.resources.limits.cpu + default: "1" + description: "PostgreSQL cpu limit" + type: string + label: PostgreSQL CPU Limit + show_if: "artifactory.postgresql.enabled=true" + - variable: artifactory.postgresql.resources.limits.memory + default: "1Gi" + description: "PostgreSQL memory limit" + type: string + label: PostgreSQL Memory Limit + show_if: "artifactory.postgresql.enabled=true" +- variable: artifactory.database.type + default: "postgresql" + description: "xternal database type (postgresql, mysql, oracle or mssql)" + type: enum + required: true + label: External Database Type + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" + options: + - "postgresql" + - "mysql" + - "oracle" + - "mssql" +- variable: artifactory.database.url + default: "" + description: "External database URL. If you set the url, leave host and port empty" + type: string + label: External Database URL + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" +- variable: artifactory.database.host + default: "" + description: "External database hostname" + type: string + label: External Database Hostname + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" +- variable: artifactory.database.port + default: "" + description: "External database port" + type: string + label: External Database Port + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" +- variable: artifactory.database.user + default: "" + description: "External database username" + type: string + label: External Database Username + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" +- variable: artifactory.database.password + default: "" + description: "External database password" + type: password + label: External Database Password + group: "Database Settings" + show_if: "artifactory.postgresql.enabled=false" + +# Advance Settings +- variable: artifactory.advancedOptions + default: false + description: "Show advanced configurations" + label: Show Advanced Configurations + type: boolean + show_subquestion_if: true + group: "Advanced Options" + subquestions: + - variable: artifactory.artifactory.primary.resources.requests.cpu + default: "500m" + description: "Artifactory primary node initial cpu request" + type: string + label: Artifactory Primary Node Initial CPU Request + - variable: artifactory.artifactory.primary.resources.requests.memory + default: "1Gi" + description: "Artifactory primary node initial memory request" + type: string + label: Artifactory Primary Node Initial Memory Request + - variable: artifactory.artifactory.primary.javaOpts.xms + default: "1g" + description: "Artifactory primary node java Xms size" + type: string + label: Artifactory Primary Node Java Xms Size + - variable: artifactory.artifactory.primary.resources.limits.cpu + default: "2" + description: "Artifactory primary node cpu limit" + type: string + label: Artifactory Primary Node CPU Limit + - variable: artifactory.artifactory.primary.resources.limits.memory + default: "4Gi" + description: "Artifactory primary node memory limit" + type: string + label: Artifactory Primary Node Memory Limit + - variable: artifactory.artifactory.primary.javaOpts.xmx + default: "4g" + description: "Artifactory primary node java Xmx size" + type: string + label: Artifactory Primary Node Java Xmx Size + - variable: artifactory.artifactory.node.resources.requests.cpu + default: "500m" + description: "Artifactory member node initial cpu request" + type: string + label: Artifactory Member Node Initial CPU Request + - variable: artifactory.artifactory.node.resources.requests.memory + default: "2Gi" + description: "Artifactory member node initial memory request" + type: string + label: Artifactory Member Node Initial Memory Request + - variable: artifactory.artifactory.node.javaOpts.xms + default: "1g" + description: "Artifactory member node java Xms size" + type: string + label: Artifactory Member Node Java Xms Size + - variable: artifactory.artifactory.node.resources.limits.cpu + default: "2" + description: "Artifactory member node cpu limit" + type: string + label: Artifactory Member Node CPU Limit + - variable: artifactory.artifactory.node.resources.limits.memory + default: "4Gi" + description: "Artifactory member node memory limit" + type: string + label: Artifactory Member Node Memory Limit + - variable: artifactory.artifactory.node.javaOpts.xmx + default: "4g" + description: "Artifactory member node java Xmx size" + type: string + label: Artifactory Member Node Java Xmx Size + +# Internal Settings +- variable: installerInfo + default: '\{\"productId\": \"RancherHelm_artifactory-jcr/7.6.3\", \"features\": \[\{\"featureId\": \"Partner/ACC-007246\"\}\]\}' + type: string + group: "Internal Settings (Do not modify)" diff --git a/charts/jfrog/artifactory-jcr/107.90.9/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.90.9/templates/NOTES.txt new file mode 100644 index 0000000000..035bf84174 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/templates/NOTES.txt @@ -0,0 +1 @@ +Congratulations. You have just deployed JFrog Container Registry! diff --git a/charts/jfrog/artifactory-jcr/107.90.9/values.yaml b/charts/jfrog/artifactory-jcr/107.90.9/values.yaml new file mode 100644 index 0000000000..1cf33285e7 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.90.9/values.yaml @@ -0,0 +1,75 @@ +# Default values for artifactory-jcr. +# This is a YAML-formatted file. + +# Beware when changing values here. You should know what you are doing! +# Access the values with {{ .Values.key.subkey }} + +# This chart is based on the main artifactory chart with some customizations. +# See all supported configuration keys in https://github.com/jfrog/charts/tree/master/stable/artifactory + +## All values are under the 'artifactory' sub chart. +artifactory: + ## Artifactory + ## See full list of supported Artifactory options and documentation in artifactory chart: https://github.com/jfrog/charts/tree/master/stable/artifactory + artifactory: + ## Default tag is from the artifactory sub-chart in the requirements.yaml + image: + registry: releases-docker.jfrog.io + repository: jfrog/artifactory-jcr + # tag: + ## Uncomment the following resources definitions or pass them from command line + ## to control the cpu and memory resources allocated by the Kubernetes cluster + resources: {} + # requests: + # memory: "1Gi" + # cpu: "500m" + # limits: + # memory: "4Gi" + # cpu: "1" + ## The following Java options are passed to the java process running Artifactory. + ## You should set them according to the resources set above. + ## IMPORTANT: Make sure resources.limits.memory is at least 1G more than Xmx. + javaOpts: {} + # xms: "1g" + # xmx: "3g" + # other: "" + installer: + platform: jcr-helm + installerInfo: '{"productId":"Helm_artifactory-jcr/{{ .Chart.Version }}","features":[{"featureId":"Platform/{{ printf "%s-%s" "kubernetes" .Capabilities.KubeVersion.Version }}"},{"featureId":"Database/{{ .Values.database.type }}"},{"featureId":"PostgreSQL_Enabled/{{ .Values.postgresql.enabled }}"},{"featureId":"Nginx_Enabled/{{ .Values.nginx.enabled }}"},{"featureId":"ArtifactoryPersistence_Type/{{ .Values.artifactory.persistence.type }}"},{"featureId":"SplitServicesToContainers_Enabled/{{ .Values.splitServicesToContainers }}"},{"featureId":"UnifiedSecretInstallation_Enabled/{{ .Values.artifactory.unifiedSecretInstallation }}"},{"featureId":"Filebeat_Enabled/{{ .Values.filebeat.enabled }}"},{"featureId":"ReplicaCount/{{ .Values.artifactory.replicaCount }}"}]}' + ## Nginx + ## See full list of supported Nginx options and documentation in artifactory chart: https://github.com/jfrog/charts/tree/master/stable/artifactory + nginx: + enabled: true + tlsSecretName: "" + service: + type: LoadBalancer + ## Ingress + ## See full list of supported Ingress options and documentation in artifactory chart: https://github.com/jfrog/charts/tree/master/stable/artifactory + ingress: + enabled: false + tls: + ## PostgreSQL + ## See list of supported postgresql options and documentation in artifactory chart: https://github.com/jfrog/charts/tree/master/stable/artifactory + ## Configuration values for the PostgreSQL dependency sub-chart + ## ref: https://github.com/bitnami/charts/blob/master/bitnami/postgresql/README.md + postgresql: + enabled: true + ## This key is required for upgrades to protect old PostgreSQL chart's breaking changes. + databaseUpgradeReady: "yes" + ## If NOT using the PostgreSQL in this chart (artifactory.postgresql.enabled=false), + ## specify custom database details here or leave empty and Artifactory will use embedded derby. + ## See full list of database options and documentation in artifactory chart: https://github.com/jfrog/charts/tree/master/stable/artifactory + # database: + jfconnect: + enabled: false + federation: + enabled: false +## Enable the PostgreSQL sub chart +postgresql: + enabled: true +router: + image: + tag: 7.118.0 +initContainers: + image: + tag: 9.4.949.1716471857 diff --git a/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml b/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml index ec37fb92d2..21d41a75ce 100644 --- a/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml +++ b/charts/kubecost/cost-analyzer/2.3.4/Chart.yaml @@ -4,7 +4,6 @@ annotations: url: https://www.kubecost.com catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Kubecost - catalog.cattle.io/featured: "1" catalog.cattle.io/release-name: cost-analyzer apiVersion: v2 appVersion: 2.3.4 diff --git a/charts/kubecost/cost-analyzer/2.3.5/Chart.yaml b/charts/kubecost/cost-analyzer/2.3.5/Chart.yaml new file mode 100644 index 0000000000..4f047595d9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/Chart.yaml @@ -0,0 +1,14 @@ +annotations: + artifacthub.io/links: | + - name: Homepage + url: https://www.kubecost.com + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Kubecost + catalog.cattle.io/featured: "1" + catalog.cattle.io/release-name: cost-analyzer +apiVersion: v2 +appVersion: 2.3.5 +description: Kubecost Helm chart - monitor your cloud costs! +icon: file://assets/icons/cost-analyzer.png +name: cost-analyzer +version: 2.3.5 diff --git a/charts/kubecost/cost-analyzer/2.3.5/README.md b/charts/kubecost/cost-analyzer/2.3.5/README.md new file mode 100644 index 0000000000..72da48c297 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/README.md @@ -0,0 +1,116 @@ +# Kubecost Helm chart + +This is the official Helm chart for [Kubecost](https://www.kubecost.com/), an enterprise-grade application to monitor and manage Kubernetes spend. Please see the [website](https://www.kubecost.com/) for more details on what Kubecost can do for you and the official documentation [here](https://docs.kubecost.com/), or contact [team@kubecost.com](mailto:team@kubecost.com) for assistance. + +To install via Helm, run the following command. + +```sh +helm upgrade --install kubecost -n kubecost --create-namespace \ + --repo https://kubecost.github.io/cost-analyzer/ cost-analyzer \ + --set kubecostToken="aGVsbUBrdWJlY29zdC5jb20=xm343yadf98" +``` + +Alternatively, add the Helm repository first and scan for updates. + +```sh +helm repo add kubecost https://kubecost.github.io/cost-analyzer/ +helm repo update +``` + +Next, install the chart. + +```sh +helm install kubecost kubecost/cost-analyzer -n kubecost --create-namespace \ + --set kubecostToken="aGVsbUBrdWJlY29zdC5jb20=xm343yadf98" +``` + +While Helm is the [recommended install path](http://kubecost.com/install) for Kubecost especially in production, Kubecost can alternatively be deployed with a single-file manifest using the following command. Keep in mind when choosing this method, Kubecost will be installed from a development branch and may include unreleased changes. + +```sh +kubectl apply -f https://raw.githubusercontent.com/kubecost/cost-analyzer-helm-chart/develop/kubecost.yaml +``` + +The following table lists commonly used configuration parameters for the Kubecost Helm chart and their default values. Please see the [values file](values.yaml) for the complete set of definable values. + +| Parameter | Description | Default | +|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------| +| `global.prometheus.enabled` | If false, use an existing Prometheus install. [More info](http://docs.kubecost.com/custom-prom). | `true` | +| `prometheus.server.persistentVolume.enabled` | If true, Prometheus server will create a Persistent Volume Claim. | `true` | +| `prometheus.server.persistentVolume.size` | Prometheus server data Persistent Volume size. Default set to retain ~6000 samples per second for 15 days. | `32Gi` | +| `prometheus.server.retention` | Determines when to remove old data. | `15d` | +| `prometheus.server.resources` | Prometheus server resource requests and limits. | `{}` | +| `prometheus.nodeExporter.resources` | Node exporter resource requests and limits. | `{}` | +| `prometheus.nodeExporter.enabled` `prometheus.serviceAccounts.nodeExporter.create` | If false, do not crate NodeExporter daemonset. | `true` | +| `prometheus.alertmanager.persistentVolume.enabled` | If true, Alertmanager will create a Persistent Volume Claim. | `true` | +| `prometheus.pushgateway.persistentVolume.enabled` | If true, Prometheus Pushgateway will create a Persistent Volume Claim. | `true` | +| `persistentVolume.enabled` | If true, Kubecost will create a Persistent Volume Claim for product config data. | `true` | +| `persistentVolume.size` | Define PVC size for cost-analyzer | `32.0Gi` | +| `persistentVolume.dbSize` | Define PVC size for cost-analyzer's flat file database | `32.0Gi` | +| `ingress.enabled` | If true, Ingress will be created | `false` | +| `ingress.annotations` | Ingress annotations | `{}` | +| `ingress.className` | Ingress class name | `{}` | +| `ingress.paths` | Ingress paths | `["/"]` | +| `ingress.hosts` | Ingress hostnames | `[cost-analyzer.local]` | +| `ingress.tls` | Ingress TLS configuration (YAML) | `[]` | +| `networkPolicy.enabled` | If true, create a NetworkPolicy to deny egress | `false` | +| `networkPolicy.costAnalyzer.enabled` | If true, create a newtork policy for cost-analzyer | `false` | +| `networkPolicy.costAnalyzer.annotations` | Annotations to be added to the network policy | `{}` | +| `networkPolicy.costAnalyzer.additionalLabels` | Additional labels to be added to the network policy | `{}` | +| `networkPolicy.costAnalyzer.ingressRules` | A list of network policy ingress rules | `null` | +| `networkPolicy.costAnalyzer.egressRules` | A list of network policy egress rules | `null` | +| `networkCosts.enabled` | If true, collect network allocation metrics [More info](http://docs.kubecost.com/network-allocation) | `false` | +| `networkCosts.podMonitor.enabled` | If true, a [PodMonitor](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#podmonitor) for the network-cost daemonset is created | `false` | +| `serviceMonitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` | +| `serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `serviceMonitor.relabelings` | Sets Prometheus metric_relabel_configs on the scrape job | `[]` | +| `serviceMonitor.metricRelabelings` | Sets Prometheus relabel_configs on the scrape job | `[]` | +| `prometheusRule.enabled` | Set this to `true` to create PrometheusRule for Prometheus operator | `false` | +| `prometheusRule.additionalLabels` | Additional labels that can be used so PrometheusRule will be discovered by Prometheus | `{}` | +| `grafana.resources` | Grafana resource requests and limits. | `{}` | +| `grafana.serviceAccount.create` | If true, create a Service Account for Grafana. | `true` | +| `grafana.serviceAccount.name` | Grafana Service Account name. | `{}` | +| `grafana.sidecar.datasources.defaultDatasourceEnabled` | Set this to `false` to disable creation of Prometheus datasource in Grafana | `true` | +| `serviceAccount.create` | Set this to `false` if you want to create the service account `kubecost-cost-analyzer` on your own | `true` | +| `tolerations` | node taints to tolerate | `[]` | +| `affinity` | pod affinity | `{}` | +| `kubecostProductConfigs.productKey.mountPath` | Use instead of `kubecostProductConfigs.productKey.secretname` to declare the path at which the product key file is mounted (eg. by a secrets provisioner) | `N/A` | +| `kubecostFrontend.api.fqdn` | Customize the upstream api FQDN | `computed in terms of the service name and namespace` | +| `kubecostFrontend.model.fqdn` | Customize the upstream model FQDN | `computed in terms of the service name and namespace` | +| `clusterController.fqdn` | Customize the upstream cluster controller FQDN | `computed in terms of the service name and namespace` | +| `global.grafana.fqdn` | Customize the upstream grafana FQDN | `computed in terms of the release name and namespace` | + +## Adjusting Log Output + +The log output can be customized during deployment by using the `LOG_LEVEL` and/or `LOG_FORMAT` environment variables. + +### Adjusting Log Level + +Adjusting the log level increases or decreases the level of verbosity written to the logs. To set the log level to `trace`, the following flag can be added to the `helm` command. + +```sh +--set 'kubecostModel.extraEnv[0].name=LOG_LEVEL,kubecostModel.extraEnv[0].value=trace' +``` + +### Adjusting Log Format + +Adjusting the log format changes the format in which the logs are output making it easier for log aggregators to parse and display logged messages. The `LOG_FORMAT` environment variable accepts the values `JSON`, for a structured output, and `pretty` for a nice, human-readable output. + +| Value | Output | +|--------|----------------------------------------------------------------------------------------------------------------------------| +| `JSON` | `{"level":"info","time":"2006-01-02T15:04:05.999999999Z07:00","message":"Starting cost-model (git commit \"1.91.0-rc.0\")"}` | +| `pretty` | `2006-01-02T15:04:05.999999999Z07:00 INF Starting cost-model (git commit "1.91.0-rc.0")` | + +## Testing +To perform local testing do next: +- install locally [kind](https://github.com/kubernetes-sigs/kind) according to documentation. +- install locally [ct](https://github.com/helm/chart-testing) according to documentation. +- create local cluster using `kind` \ +use image version from https://github.com/kubernetes-sigs/kind/releases e.g. `kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8` +```shell +kind create cluster --image kindest/node:v1.25.11@sha256:227fa11ce74ea76a0474eeefb84cb75d8dad1b08638371ecf0e86259b35be0c8 +``` +- perform ct execution +```shell +ct install --chart-dirs="." --charts="." +``` + diff --git a/charts/kubecost/cost-analyzer/2.3.5/app-readme.md b/charts/kubecost/cost-analyzer/2.3.5/app-readme.md new file mode 100644 index 0000000000..90fd50607a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/app-readme.md @@ -0,0 +1,25 @@ +# Kubecost + +[Kubecost](https://kubecost.com/) is an open-source Kubernetes cost monitoring solution. + +Kubecost gives teams visibility into current and historical Kubernetes spend and resource allocation. These models provide cost transparency in Kubernetes environments that support multiple applications, teams, departments, etc. + +To see more on the functionality of the full Kubecost product, please visit the [features page](https://kubecost.com/#features) on our website. + +Here is a summary of features enabled by this cost model: + +- Real-time cost allocation by Kubernetes service, deployment, namespace, label, statefulset, daemonset, pod, and container +- Dynamic asset pricing enabled by integrations with AWS, Azure, and GCP billing APIs +- Supports on-prem k8s clusters with custom pricing sheets +- Allocation for in-cluster resources like CPU, GPU, memory, and persistent volumes. +- Allocation for AWS & GCP out-of-cluster resources like RDS instances and S3 buckets with key (optional) +- Easily export pricing data to Prometheus with /metrics endpoint ([learn more](https://github.com/kubecost/cost-model/blob/develop/PROMETHEUS.md)) +- Free and open source distribution (Apache2 license) + +## Requirements + +- Kubernetes 1.8+ +- kube-state-metrics +- Grafana +- Prometheus +- Node Exporter diff --git a/charts/kubecost/cost-analyzer/2.3.5/ci/aggregator-values.yaml b/charts/kubecost/cost-analyzer/2.3.5/ci/aggregator-values.yaml new file mode 100644 index 0000000000..523b9e81b5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/ci/aggregator-values.yaml @@ -0,0 +1,17 @@ +kubecostAggregator: + enabled: true + cloudCost: + enabled: true + aggregatorDbStorage: + storageRequest: 10Gi +kubecostModel: + federatedStorageConfigSecret: federated-store +kubecostProductConfigs: + cloudIntegrationSecret: cloud-integration + clusterName: CLUSTER_NAME +prometheus: + server: + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME diff --git a/charts/kubecost/cost-analyzer/2.3.5/ci/federatedetl-primary-netcosts-values.yaml b/charts/kubecost/cost-analyzer/2.3.5/ci/federatedetl-primary-netcosts-values.yaml new file mode 100644 index 0000000000..78ad057258 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/ci/federatedetl-primary-netcosts-values.yaml @@ -0,0 +1,35 @@ +kubecostProductConfigs: + clusterName: CLUSTER_NAME + # cloudIntegrationSecret: cloud-integration +federatedETL: + useExistingS3Config: false + federatedCluster: true +kubecostModel: + containerStatsEnabled: true + federatedStorageConfigSecret: federated-store +serviceAccount: # this example uses AWS IRSA, which creates a service account with rights to the s3 bucket. If using keys+secrets in the federated-store, set create: true + create: true +global: + prometheus: + enabled: true + # fqdn: http://prometheus-operated.monitoring:9090 + grafana: # prometheus metrics will be local cluster only, disable grafana to save resources + enabled: false + proxy: false +prometheus: + nodeExporter: + enabled: false + server: + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME +networkCosts: + # optional, see: https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration + enabled: true + config: + services: + # set the appropriate cloud provider to true + amazon-web-services: true + # google-cloud-services: true + # azure-cloud-services: true diff --git a/charts/kubecost/cost-analyzer/2.3.5/ci/statefulsets-cc.yaml b/charts/kubecost/cost-analyzer/2.3.5/ci/statefulsets-cc.yaml new file mode 100644 index 0000000000..626a0c2e5e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/ci/statefulsets-cc.yaml @@ -0,0 +1,46 @@ +### This test is to verify that Kubecost aggregator is deployed as a StatefulSet, +### cluster controller is installed, and the various Prometheus components are installed. +global: + podAnnotations: + kubecost.io/test1: value1 + kubecost.io/test2: value2 + additionalLabels: + kubecosttest1: value1 + kubecosttest2: value2 + prometheus: + enabled: true + # fqdn: http://prometheus-operated.monitoring:9090 + grafana: # prometheus metrics will be local cluster only, disable grafana to save resources + enabled: false + proxy: false +kubecostProductConfigs: + clusterName: CLUSTER_NAME +kubecostAggregator: + deployMethod: statefulset +kubecostModel: + federatedStorageConfigSecret: federated-store +clusterController: + enabled: true + actionConfigs: + clusterTurndown: + - name: my-schedule2 + start: "2034-02-09T00:00:00Z" + end: "2034-02-09T01:00:00Z" + repeat: none +prometheus: + nodeExporter: + enabled: true + alertmanager: + enabled: true + configmapReload: + prometheus: + enabled: true + pushgateway: + enabled: true + server: + statefulSet: + enabled: true + global: + external_labels: + # cluster_id should be unique for all clusters and the same value as .kubecostProductConfigs.clusterName + cluster_id: CLUSTER_NAME \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/crds/cluster-turndown-crd.yaml b/charts/kubecost/cost-analyzer/2.3.5/crds/cluster-turndown-crd.yaml new file mode 100644 index 0000000000..8c87644cc9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/crds/cluster-turndown-crd.yaml @@ -0,0 +1,78 @@ +# TurndownSchedule Custom Resource Definition for persistence +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: turndownschedules.kubecost.com +spec: + group: kubecost.com + names: + kind: TurndownSchedule + singular: turndownschedule + plural: turndownschedules + shortNames: + - td + - tds + scope: Cluster + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + start: + type: string + format: date-time + end: + type: string + format: date-time + repeat: + type: string + enum: [none, daily, weekly] + status: + type: object + properties: + state: + type: string + lastUpdated: + format: date-time + type: string + current: + type: string + scaleDownId: + type: string + nextScaleDownTime: + format: date-time + type: string + scaleDownMetadata: + additionalProperties: + type: string + type: object + scaleUpID: + type: string + nextScaleUpTime: + format: date-time + type: string + scaleUpMetadata: + additionalProperties: + type: string + type: object + additionalPrinterColumns: + - name: State + type: string + description: The state of the turndownschedule + jsonPath: .status.state + - name: Next Turndown + type: string + description: The next turndown date-time + jsonPath: .status.nextScaleDownTime + - name: Next Turn Up + type: string + description: The next turn up date-time + jsonPath: .status.nextScaleUpTime diff --git a/charts/kubecost/cost-analyzer/2.3.5/custom-pricing.csv b/charts/kubecost/cost-analyzer/2.3.5/custom-pricing.csv new file mode 100644 index 0000000000..c3e6d23674 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/custom-pricing.csv @@ -0,0 +1,7 @@ +EndTimestamp,InstanceID,Region,AssetClass,InstanceIDField,InstanceType,MarketPriceHourly,Version +2028-01-06 23:34:45 UTC,,us-east-2,node,metadata.name,g4dn.xlarge,5.55, +2028-01-06 23:34:45 UTC,,,node,metadata.name,R730-type1,1.35, +2028-01-06 23:34:45 UTC,,,pv,metadata.name,standard,0.44, +2028-01-06 23:34:45 UTC,a100,,gpu,gpu.nvidia.com/class,,0.75, +2028-01-06 23:34:45 UTC,RTX3090,,gpu,nvidia.com/gpu_type,,0.65, +2028-01-06 23:34:45 UTC,i-01045ab6d13179700,,,spec.providerID,,1.2, diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/README.md b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/README.md new file mode 100644 index 0000000000..160316ab6c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/README.md @@ -0,0 +1,45 @@ +# Kubecost Grafana Dashboards + +## Overview + +Kubecost, by default, is bundled with a Grafana instance that already contains the dashboards in this folder. + +The dashboards in this repo are imported into Kubecost, unless disabled with + + +The same dashboards have template versions in [grafana-templates/](grafana-templates/) for those wanting to load the dashboards into an existing Grafana instance. + +## Caveats + +The primary purpose of the dashboards provided is to allow visibility into the metrics used by Kubecost to create the cost-model. + +The networkCosts-metrics dashboard requires the optional networkCosts daemonset to be [enabled](https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration). + +## Metrics Required + +`kubecost-container-stats` metrics: + +``` +container_cpu_usage_seconds_total +kube_pod_container_resource_requests +container_memory_working_set_bytes +container_cpu_cfs_throttled_periods_total +container_cpu_cfs_periods_total +``` + +`network-transfer-data` metrics: + +``` +kubecost_pod_network_ingress_bytes_total +kubecost_pod_network_egress_bytes_total +``` + +`disk-usage` metrics: +``` +container_fs_limit_bytes +container_fs_usage_bytes +``` + +## Additional Information + +Kubecost Grafana [Configuration Guide](https://docs.kubecost.com/install-and-configure/advanced-configuration/custom-grafana) \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/attached-disks.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/attached-disks.json new file mode 100644 index 0000000000..49c8d6c1a0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/attached-disks.json @@ -0,0 +1,549 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 16, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_limit_bytes{instance=~'$disk', device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_limit_bytes{instance=~'$disk',device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id,instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}-{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "1 - sum(container_fs_inodes_free{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_inodes_total{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "iNode Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Usage", + "type": "timeseries" + } + ], + "schemaVersion": 39, + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "ip-192-168-147-146.us-east-2.compute.internal", + "value": "ip-192-168-147-146.us-east-2.compute.internal" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "disk", + "options": [], + "query": { + "query": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Attached disk metrics", + "uid": "nBH7qBgMk", + "version": 7, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-metrics.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-metrics.json new file mode 100644 index 0000000000..2535560006 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-metrics.json @@ -0,0 +1,1683 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Cost metrics from the Kubecost product", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 7, + "iteration": 1558062099204, + "links": [], + "panels": [ + { + "content": "Deprecated - It is not expected to match Kubecost UI/API.", + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 27, + "links": [], + "mode": "markdown", + "title": "", + "transparent": true, + "type": "text" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of CPU + GPU costs based on currently provisioned resources.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 2 + }, + "hideTimeOverride": true, + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "CPU Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of memory costs based on currently provisioned expenses.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 2 + }, + "hideTimeOverride": true, + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n)", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Memory Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Monthly run rate of attached storage and PV costs based on currently provisioned resources.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 2 + }, + "hideTimeOverride": true, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Storage Cost", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Sum of compute, memory, storage and network costs.", + "format": "currencyUSD", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 2 + }, + "hideTimeOverride": true, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "thresholds": "", + "timeFrom": "15m", + "timeShift": null, + "title": "Total Cost", + "type": "singlestat", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Current CPU use from applications divided by allocatable CPUs", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 13, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "timeFrom": "", + "title": "CPU Utilization", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "Current CPU reservation requests from applications vs allocatable CPU", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 5 + }, + "height": "180px", + "id": 15, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "title": "CPU Requests", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "description": "Current RAM use vs RAM available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 17, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "SUM(container_memory_usage_bytes{namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "B" + } + ], + "thresholds": "30,80", + "timeFrom": "", + "title": "RAM Utilization", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "description": "Current RAM requests vs RAM available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 5 + }, + "height": "180px", + "id": 19, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30,80", + "title": "RAM Requests", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "datasource": "${datasource}", + "decimals": 2, + "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "height": "180px", + "hideTimeOverride": true, + "id": 21, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(pod_pvc_allocation) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace)\n + on (persistentvolumeclaim, namespace)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "thresholds": "30, 80", + "timeFrom": "", + "title": "Storage Utilization", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of CPU + GPU costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 6, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "compute cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Compute Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of memory costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 9, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Memory Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of attached disk + PV storage costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 10, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "storage cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Storage Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Sum of compute, memory, and storage costs", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 22, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "# Compute\nsum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n\n# Memory\nsum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n) +\n\n# Storage \n\nsum(avg(pv_hourly_cost) by (persistentvolume) * 730 * avg(kube_persistentvolume_capacity_bytes) by (persistentvolume) / 1024 / 1024 / 1024) \n+\nsum(sum(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "total cost", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Total Cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "columns": [], + "datasource": "${datasource}", + "description": "Cost of by resource class of currently provisioned nodes", + "fontSize": "100%", + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 8, + "links": [], + "pageSize": null, + "scroll": true, + "showHeader": true, + "sort": { + "col": 4, + "desc": false + }, + "styles": [ + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Compute Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "CPU Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Mem Cost", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Total", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "instance", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "GPU", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #D", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + }, + { + "expr": "avg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "D" + }, + { + "expr": "# CPU \navg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost or up * 0) by (node) * 730 * (1-$useDiscount/100) +\n# GPU\navg(node_gpu_hourly_cost) by (node) * 730 * (1-$useDiscount/100) +\n# Memory\navg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730 * (1-$useDiscount/100)\n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + } + ], + "title": "Cost by node", + "transform": "table", + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Monthly run rate of attached disk + PV storage costs based on currently provisioned resources.", + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 25, + "interval": "1m", + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node) * avg(node_cpu_hourly_cost) by (node) * 730 +\n avg(node_gpu_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "cpu", + "refId": "B" + }, + { + "expr": "sum(\n avg(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node) / 1024 / 1024 / 1024 * avg(node_ram_hourly_cost) by (node) * 730\n)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "memory", + "refId": "A" + }, + { + "expr": "sum(\n avg(avg_over_time(pv_hourly_cost[$timeRange] offset 1m)) by (persistentvolume) * 730 \n * avg(avg_over_time(kube_persistentvolume_capacity_bytes[$timeRange] offset 1m)) by (persistentvolume) / 1024 / 1024 / 1024\n) +\nsum(avg(container_fs_limit_bytes{device!=\"tmpfs\", id=\"/\"}) by (instance) / 1024 / 1024 / 1024) * $localStorageGBCost", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "storage", + "refId": "C" + }, + { + "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $percentEgress * $egressCost ", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "network", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Cost by Resource", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "auto": true, + "auto_count": 1, + "auto_min": "1m", + "current": { + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + "hide": 2, + "label": null, + "name": "timeRange", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + }, + { + "selected": false, + "text": "90d", + "value": "90d" + } + ], + "query": "1h,6h,12h,1d,7d,14d,30d,90d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "current": { + "text": ".04", + "value": ".04" + }, + "hide": 2, + "label": "Cost per Gb hour for attached disks", + "name": "localStorageGBCost", + "options": [ + { + "selected": true, + "text": ".04", + "value": ".04" + } + ], + "query": ".04", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "tags": [], + "text": "0", + "value": "0" + }, + "hide": 0, + "label": "Sustained Use Discount %", + "name": "useDiscount", + "options": [ + { + "selected": true, + "text": "0", + "value": "0" + } + ], + "query": "0", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "text": ".1", + "value": ".1" + }, + "hide": 2, + "label": null, + "name": "percentEgress", + "options": [ + { + "selected": true, + "text": ".1", + "value": ".1" + } + ], + "query": ".1", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "text": ".12", + "value": ".12" + }, + "hide": 2, + "label": null, + "name": "egressCost", + "options": [ + { + "selected": true, + "text": ".12", + "value": ".12" + } + ], + "query": ".12", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Deprecated - Kubecost cluster metrics", + "uid": "JOUdHGZZz", + "version": 20 +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-utilization.json new file mode 100644 index 0000000000..8a17f26c04 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/cluster-utilization.json @@ -0,0 +1,3196 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "A dashboard to help manage Kubernetes cluster costs and resources", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 6873, + "graphTooltip": 0, + "id": 10, + "iteration": 1645112913364, + "links": [], + "liveNow": false, + "panels": [ + { + "gridPos": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 86, + "links": [], + "options": { + "content": "Deprecated - It is not expected to match Kubecost UI/API. This dashboard shows monthly cost estimates for the cluster, based on **current** CPU, RAM and storage provisioned.", + "mode": "markdown" + }, + "pluginVersion": "8.3.2", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 2 + }, + "hideTimeOverride": true, + "id": 75, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) ", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "CPU Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 2 + }, + "hideTimeOverride": true, + "id": 77, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n) ", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "RAM Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 2 + }, + "hideTimeOverride": true, + "id": 78, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Storage Cost (Cluster and PVC)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Represents a near worst-case approximation of network costs.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 2 + }, + "hideTimeOverride": true, + "id": 129, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "SUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Network Egress Cost", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current CPU use from applications divided by allocatable CPUs", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 6 + }, + "hideTimeOverride": true, + "id": 82, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "(\n sum(\n count(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n * on (instance) \n sum(irate(container_cpu_usage_seconds_total{id=\"/\"}[10m])) by (instance)\n ) \n / \n (sum (kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}))\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "CPU Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current CPU reservation requests from applications vs allocatable CPU", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 6 + }, + "id": 91, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) / SUM(kube_node_status_allocatable{resource=\"cpu\", unit=\"core\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "CPU Requests", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current RAM use vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 6 + }, + "hideTimeOverride": true, + "id": 80, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "SUM(container_memory_working_set_bytes{name!=\"POD\", container!=\"\", namespace!=\"\"}) / SUM(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"}) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "B" + } + ], + "timeFrom": "", + "title": "RAM Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "Current RAM requests vs RAM available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 6 + }, + "id": 92, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"})\n /\n sum(kube_node_status_allocatable{resource=\"memory\", unit=\"byte\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "title": "RAM Requests", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "This gauge shows the current standard storage use, including cluster storage, vs storage available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 12, + "y": 6 + }, + "hideTimeOverride": true, + "id": 95, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) /\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n + sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"})\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "Storage Utilization", + "type": "gauge" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "description": "This gauge shows the current SSD use vs SSD available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(245, 54, 54, 0.9)", + "value": null + }, + { + "color": "rgba(50, 172, 45, 0.97)", + "value": 30 + }, + { + "color": "#c15c17", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 15, + "y": 6 + }, + "hideTimeOverride": true, + "id": 96, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace)\n) /\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace)\n) * 100", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "refId": "A", + "step": 10 + } + ], + "timeFrom": "", + "title": "SSD Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Expected monthly cost given current CPU, memory storage, and network resource consumption", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 6 + }, + "hideTimeOverride": true, + "id": 93, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "8.3.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "# CPU\nsum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) \n\n+ \n\n# Storage\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard \n\n+\n\n# END STORAGE\n# RAM \nsum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n)\n\n+\n\n#Network \nSUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Total Monthly Cost", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "description": "Expected monthly CPU, memory and storage costs given provisioned resources", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 120, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "# CPU\nsum(\n (\n (\n sum(kube_node_status_capacity_cpu_cores) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) * $costpcpu\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) * ($costcpu - ($costcpu / 100 * $costDiscount))\n )\n) \n\n+ \n\n# Storage\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageSSD\n\n+\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) or up * 0\n) / 1024 / 1024 /1024 * $costStorageStandard\n\n+ \n\nsum(container_fs_limit_bytes{id=\"/\"}) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard \n\n+\n\n# END STORAGE\n# RAM \nsum(\n (\n (\n sum(kube_node_status_capacity_memory_bytes) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible=\"true\"}) by (node)\n ) /1024/1024/1024 * $costpram\n )\n or\n (\n (\n sum(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)\n * on (node) group_left (label_cloud_google_com_gke_preemptible)\n avg(kube_node_labels{label_cloud_google_com_gke_preemptible!=\"true\"}) by (node)\n ) /1024/1024/1024 * ($costram - ($costram / 100 * $costDiscount))\n)\n) \n\n+\n\n#Network \nSUM(rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]) / 1024 / 1024 / 1024 ) * (60 * 60 * 24 * 30) * $costEgress", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "cluster cost", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Total monthly cost", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "currencyUSD", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Resources allocated to namespace based on container requests", + "fontSize": "100%", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "hideTimeOverride": false, + "id": 73, + "links": [], + "pageSize": 10, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 7, + "desc": true + }, + "styles": [ + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "pattern": "namespace", + "thresholds": [ + "30", + "80" + ], + "type": "string", + "unit": "currencyUSD" + }, + { + "alias": "RAM", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "CPU", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "PV Storage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Total", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #D", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "CPU Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#bf1b00", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #E", + "thresholds": [ + "30", + "80" + ], + "type": "number", + "unit": "percent" + }, + { + "alias": "RAM Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #F", + "thresholds": [ + "30", + "80" + ], + "type": "number", + "unit": "percent" + } + ], + "targets": [ + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"}*($costcpu - ($costcpu / 100 * $costDiscount))) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"}*$costpcpu) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "A" + }, + { + "expr": "(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"} / 1024 / 1024 / 1024*($costram- ($costram / 100 * $costDiscount))) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"} / 1024 / 1024 / 1024 * $costpram ) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "B" + }, + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageSSD \n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageStandard", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "C" + }, + { + "expr": "# CPU \n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"}*($costcpu - ($costcpu / 100 * $costDiscount))) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"}*$costpcpu) by(namespace)\n or\n count(\n count(container_spec_cpu_shares{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n#END CPU \n# Memory \n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible!=\"true\"} / 1024 / 1024 / 1024*($costram- ($costram / 100 * $costDiscount))) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n(\n sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\",namespace!=\"kube-system\",cloud_google_com_gke_preemptible=\"true\"} / 1024 / 1024 / 1024 * $costpram ) by (namespace) \n or\n count(\n count(container_spec_memory_limit_bytes{namespace!=\"\",namespace!=\"kube-system\"}) by(namespace)\n ) by(namespace) -1\n)\n\n+\n\n# PV storage\n\n(\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageSSD \n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim, namespace) \n) by (namespace) / 1024 / 1024 /1024 * $costStorageStandard \n)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Total", + "refId": "D" + } + ], + "timeFrom": "", + "title": "Namespace cost allocation", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 108, + "panels": [], + "title": "CPU Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 19 + }, + "hiddenSeries": false, + "id": 116, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "capacity", + "refId": "A" + }, + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "requests", + "refId": "C" + }, + { + "expr": "SUM(irate(container_cpu_usage_seconds_total{id=\"/\"}[5m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "B" + }, + { + "expr": "SUM(kube_pod_container_resource_limits{resource=\"cpu\", unit=\"core\"}) ", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limits", + "refId": "D" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Cluster CPUs", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 27 + }, + "hiddenSeries": false, + "id": 130, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(node_cpu_seconds_total{mode!=\"idle\"}[5m])) by (mode) * 100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{mode}}", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU Mode", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "percent", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "This table shows the comparison of CPU requests and usage by namespace", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 35 + }, + "hideTimeOverride": true, + "id": 104, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "CPU Requests", + "align": "auto", + "colors": [ + "#fceaca", + "#fce2de", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#cffaff" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "24h CPU Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [ + "30" + ], + "type": "number", + "unit": "none" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace!=\"\"}) by (namespace) ", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "sum (rate (container_cpu_usage_seconds_total{image!=\"\",namespace!=\"\"}[24h])) by (namespace)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "{{ namespace }}", + "refId": "C" + } + ], + "title": "CPU request utilization by namespace", + "transform": "table", + "type": "table-old" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "This table shows the comparison of application CPU usage vs the capacity of the node (measured over last 60 minutes)", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 35 + }, + "hideTimeOverride": true, + "id": 90, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 2, + "desc": true + }, + "styles": [ + { + "alias": "CPU Request Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + ".30", + " .80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Utilization", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + ".20", + " .80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "24h Utilization ", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "percentunit" + } + ], + "targets": [ + { + "expr": "SUM(\nSUM(rate(container_cpu_usage_seconds_total[24h])) by (pod_name)\n* on (pod_name) group_left (node) \nlabel_replace(\n avg(kube_pod_info{}),\n \"pod_name\", \n \"$1\", \n \"pod\", \n \"(.+)\"\n)\n) by (node) \n/ \nsum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (node) / sum(kube_node_status_capacity{resource=\"cpu\", unit=\"core\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Cluster cost & utilization by node", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 113, + "panels": [], + "title": "Memory Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 117, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "capacity", + "refId": "A" + }, + { + "expr": "SUM(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "requests", + "refId": "C" + }, + { + "expr": "SUM(container_memory_usage_bytes{image!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "B" + }, + { + "expr": "SUM(kube_pod_container_resource_limits{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limits", + "refId": "D" + } + ], + "thresholds": [], + "title": "Cluster memory (GB)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decgbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 131, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "1 - sum(node_memory_MemAvailable_bytes) by (node) / sum(node_memory_MemTotal_bytes) by (node)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "title": "Cluster Memory Utilization", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percentunit", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Comparison of memory requests and current usage by namespace", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 62 + }, + "hideTimeOverride": true, + "id": 109, + "links": [], + "pageSize": 7, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "Mem Requests (GB)", + "align": "auto", + "colors": [ + "#fceaca", + "#fce2de", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "CPU Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#cffaff" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "" + ], + "type": "number", + "unit": "short" + }, + { + "alias": "24h Mem Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "#508642", + "#e5ac0e" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [ + ".30", + ".75" + ], + "type": "number", + "unit": "none" + }, + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"} / 1024 / 1024 / 1024) by (namespace) ", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + }, + { + "expr": "SUM(container_memory_usage_bytes{image!=\"\",namespace!=\"\"} / 1024 / 1024 / 1024) by (namespace)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "C" + } + ], + "title": "Memory requests & utilization by namespace", + "transform": "table", + "type": "table-old" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "description": "Container RAM usage vs node capacity", + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 62 + }, + "hideTimeOverride": true, + "id": 114, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": true + }, + "styles": [ + { + "alias": "RAM Requests", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [ + "30", + " 80" + ], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "node", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "RAM Usage", + "align": "auto", + "colorMode": "value", + "colors": [ + "#ef843c", + "rgba(50, 172, 45, 0.97)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [ + "25", + " 80" + ], + "type": "number", + "unit": "percent" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "SUM(label_replace(container_memory_usage_bytes{namespace!=\"\"}, \"node\", \"$1\", \"instance\",\"(.+)\")) by (node) * 100\n/\nSUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "sum(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", namespace!=\"\"}) by (node) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\"}) by (node)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Node utilization of allocatable RAM", + "transform": "table", + "type": "table-old" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 72 + }, + "id": 101, + "panels": [], + "title": "Storage Metrics", + "type": "row" + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "fontSize": "100%", + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 73 + }, + "hideTimeOverride": true, + "id": 97, + "links": [], + "pageSize": 8, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 4, + "desc": true + }, + "styles": [ + { + "alias": "Node", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "instance", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "PVC Name", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "persistentvolumeclaim", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Storage Class", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "storageclass", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Size (GB)", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "percentunit" + } + ], + "targets": [ + { + "expr": "SUM(container_fs_limit_bytes{id=\"/\"}) by (instance) / 1024 / 1024 / 1024 * 1.03", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + }, + { + "expr": "SUM(container_fs_limit_bytes{id=\"/\"}) by (instance) / 1024 / 1024 / 1024 * 1.03 * $costStorageStandard\n", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + }, + { + "expr": "sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"} / container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\"}) by (instance) \n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + } + ], + "title": "Local Storage", + "transform": "table", + "type": "table-old" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 73 + }, + "id": 128, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(container_fs_usage_bytes{id=\"/\"}) / SUM(container_fs_limit_bytes{id=\"/\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "reads", + "refId": "D" + } + ], + "thresholds": [], + "title": "Local storage utilization", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "IOPS", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "columns": [ + { + "text": "Avg", + "value": "avg" + } + ], + "datasource": { + "uid": "${datasource}" + }, + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 82 + }, + "hideTimeOverride": true, + "id": 94, + "links": [], + "pageSize": 10, + "repeatDirection": "v", + "scroll": true, + "showHeader": true, + "sort": { + "col": 2, + "desc": true + }, + "styles": [ + { + "alias": "Namespace", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTooltip": "View namespace cost metrics", + "linkUrl": "d/at-cost-analysis-namespace2/namespace-cost-metrics?&var-namespace=$__cell", + "mappingType": 1, + "pattern": "namespace", + "thresholds": [], + "type": "string", + "unit": "short" + }, + { + "alias": "PVC Name", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "persistentvolumeclaim", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Storage Class", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "storageclass", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Cost", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #A", + "thresholds": [], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "Usage", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #B", + "thresholds": [], + "type": "number", + "unit": "percentunit" + }, + { + "alias": "Size (GB)", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "Value #C", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024\n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024\n\n\n", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "C" + }, + { + "expr": "sum (\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 * $costStorageSSD\n\nor\n\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 * $costStorageStandard\n", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ persistentvolumeclaim }}", + "refId": "A" + }, + { + "expr": "sum(kubelet_volume_stats_used_bytes) by (persistentvolumeclaim, namespace) \n/\nsum (\n sum(kube_persistentvolumeclaim_info{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace, storageclass)\n * on (persistentvolumeclaim, namespace) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{storageclass!~\".*ssd.*\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "refId": "B" + } + ], + "title": "Persistent Volume Claims", + "transform": "table", + "type": "table-old" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 82 + }, + "id": 132, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM(rate(node_disk_reads_completed_total[10m])) or SUM(rate(node_disk_reads_completed[10m]))\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "reads", + "refId": "D" + }, + { + "expr": "SUM(rate(node_disk_writes_completed_total[10m])) or SUM(rate(node_disk_writes_completed[10m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "writes", + "refId": "A" + } + ], + "thresholds": [], + "title": "Disk IOPS", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": "IOPS", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 92 + }, + "id": 122, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "SUM( kubelet_volume_stats_inodes_used / kubelet_volume_stats_inodes) by (persistentvolumeclaim) * 100", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "refId": "D" + } + ], + "thresholds": [], + "title": "Inode usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 101 + }, + "id": 127, + "panels": [], + "title": "Network", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 102 + }, + "id": 123, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "8.3.2", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (rate (node_network_transmit_bytes_total{}[60m]))\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "node_out", + "refId": "B" + }, + { + "expr": "SUM ( rate(node_network_transmit_bytes_total{device=\"eth0\"}[60m]))", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "eth0 out", + "refId": "C" + } + ], + "thresholds": [], + "title": "Node network transmit", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "15m", + "schemaVersion": 33, + "style": "dark", + "tags": [ + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "23.076", + "value": "23.076" + }, + "hide": 0, + "label": "CPU", + "name": "costcpu", + "options": [ + { + "selected": true, + "text": "23.076", + "value": "23.076" + } + ], + "query": "23.076", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "5.10", + "value": "5.10" + }, + "hide": 0, + "label": "PE CPU", + "name": "costpcpu", + "options": [ + { + "selected": true, + "text": "5.10", + "value": "5.10" + } + ], + "query": "5.10", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "3.25", + "value": "3.25" + }, + "hide": 0, + "label": "RAM", + "name": "costram", + "options": [ + { + "selected": true, + "text": "3.25", + "value": "3.25" + } + ], + "query": "3.25", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "0.6862", + "value": "0.6862" + }, + "hide": 0, + "label": "PE RAM", + "name": "costpram", + "options": [ + { + "selected": true, + "text": "0.6862", + "value": "0.6862" + } + ], + "query": "0.6862", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "0.040", + "value": "0.040" + }, + "hide": 0, + "label": "Storage", + "name": "costStorageStandard", + "options": [ + { + "selected": true, + "text": "0.040", + "value": "0.040" + } + ], + "query": "0.040", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": ".17", + "value": ".17" + }, + "hide": 0, + "label": "SSD", + "name": "costStorageSSD", + "options": [ + { + "selected": true, + "text": ".17", + "value": ".17" + } + ], + "query": ".17", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": ".12", + "value": ".12" + }, + "hide": 0, + "label": "Egress", + "name": "costEgress", + "options": [ + { + "selected": true, + "text": ".12", + "value": ".12" + } + ], + "query": ".12", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": "30", + "value": "30" + }, + "hide": 0, + "label": "Discount", + "name": "costDiscount", + "options": [ + { + "selected": true, + "text": "30", + "value": "30" + } + ], + "query": "30", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": false, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Deprecated - Cluster cost & utilization metrics", + "uid": "cluster-costs", + "version": 1, + "weekStart": "" + } \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/deployment-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/deployment-utilization.json new file mode 100644 index 0000000000..1fd2b1d9ee --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/deployment-utilization.json @@ -0,0 +1,1386 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Monitors Kubernetes deployments in cluster using Prometheus and kube-state-metrics. Shows resource utilization of deployments, daemonsets, and statefulsets.", + "editable": true, + "gnetId": 8588, + "graphTooltip": 0, + "id": 2, + "iteration": 1586200623748, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 0 + }, + "height": "180px", + "id": 1, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (container_memory_working_set_bytes{container!=\"\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\", pod_name!=\"\"}) / sum (kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node.*$\"}) * 100", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "refId": "A", + "step": 900 + } + ], + "thresholds": "65, 90", + "title": "Deployment memory usage", + "transparent": false, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 0 + }, + "height": "180px", + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (rate (container_cpu_usage_seconds_total{pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\"}[2m])) / sum (machine_cpu_cores{kubernetes_io_hostname=~\"^$Node$\"}) * 100", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "refId": "A", + "step": 900 + } + ], + "thresholds": "65, 90", + "title": "Deployment CPU usage", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 0 + }, + "height": "180px", + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(((sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))) - ((sum(kube_deployment_status_replicas_available{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_status_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_number_ready{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)))) / ((sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))) * 100", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "1,30", + "title": "Unavailable Replicas", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "bytes", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 5 + }, + "height": "100px", + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(container_memory_working_set_bytes{container!=\"\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\", pod_name!=\"\"})", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Used", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "bytes", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 5 + }, + "height": "100px", + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node.*$\"})", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 8, + "y": 5 + }, + "height": "100px", + "id": 6, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": " cores", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (rate (container_cpu_usage_seconds_total{pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\", kubernetes_io_hostname=~\"^$Node$\"}[5m]))", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Used", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 12, + "y": 5 + }, + "height": "100px", + "id": 7, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": " cores", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum (machine_cpu_cores{kubernetes_io_hostname=~\"^$Node$\"})", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 5 + }, + "height": "100px", + "id": 8, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(sum(kube_deployment_status_replicas_available{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_status_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_number_ready{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Available (cluster)", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "${datasource}", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 5 + }, + "height": "100px", + "id": 9, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "(sum(kube_deployment_status_replicas{deployment=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_statefulset_replicas{statefulset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0)) + (sum(kube_daemonset_status_desired_number_scheduled{daemonset=~\".*$Deployment$Statefulset$Daemonset\"}) or vector(0))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ $Daemonset }}", + "refId": "A", + "step": 1800 + } + ], + "thresholds": "", + "title": "Total (cluster)", + "type": "singlestat", + "valueFontSize": "50%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 3, + "editable": true, + "error": false, + "fill": 0, + "grid": {}, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 8 + }, + "height": "", + "id": 10, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/avlbl.*/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (irate (container_cpu_usage_seconds_total{container!=\"\",image!=\"\",name=~\"^k8s_.*\",io_kubernetes_container_name!=\"POD\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[5m])) by (pod_name,kubernetes_io_hostname)", + "format": "time_series", + "hide": false, + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "usage: {{ kubernetes_io_hostname }} | {{ pod_name }} ", + "metric": "container_cpu", + "refId": "A", + "step": 60 + }, + { + "expr": "sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", pod=~\"^$Deployment$Statefulset$Daemonset.*$\",node=~\"^$Node$\"}) by (pod,node)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "rqst: {{ node }} | {{ pod }}", + "refId": "B", + "step": 120 + }, + { + "expr": "sum ((kube_node_status_allocatable{resource=\"cpu\", unit=\"core\", node=~\"^$Node$\"})) by (node)", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "avlbl: {{ node }}", + "refId": "C", + "step": 30 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "CPU usage & requests", + "tooltip": { + "msResolution": true, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": "cores", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fill": 0, + "grid": {}, + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 11, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/^avlbl.*$/", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max (container_memory_working_set_bytes{container!=\"POD\",container!=\"\",id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}) by (pod_name,kubernetes_io_hostname)", + "format": "time_series", + "hide": false, + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "usage: {{kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "container_memory_usage:sort_desc", + "refId": "A", + "step": 60 + }, + { + "expr": "sum ((kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", pod=~\"^$Deployment$Statefulset$Daemonset.*$\",node=~\"^$Node$\"})) by (pod,node)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "rqst: {{ node }} | {{ pod }}", + "refId": "B", + "step": 120 + }, + { + "expr": "sum ((kube_node_status_allocatable{resource=\"memory\", unit=\"byte\", node=~\"^$Node$\"})) by (node)", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "avlbl: {{ node }}", + "refId": "C", + "step": 30 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Memory usage & requests", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 12, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "100 * (kubelet_volume_stats_used_bytes{kubernetes_io_hostname=~\"^$Node$\", persistentvolumeclaim=~\".*$Deployment$Statefulset$Daemonset.*$\"} / kubelet_volume_stats_capacity_bytes{kubernetes_io_hostname=~\"^$Node$\", persistentvolumeclaim=~\".*$Deployment$Statefulset$Daemonset.*$\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ persistentvolumeclaim }} | {{ kubernetes_io_hostname }}", + "refId": "A", + "step": 120 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Disk Usage", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fill": 1, + "grid": {}, + "gridPos": { + "h": 13, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 13, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum (rate (container_network_receive_bytes_total{id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[2m])) by (pod_name, kubernetes_io_hostname)", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "-> {{ kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "network", + "refId": "A", + "step": 60 + }, + { + "expr": "- sum( rate (container_network_transmit_bytes_total{id!=\"/\",pod_name=~\"^$Deployment$Statefulset$Daemonset.*$\",kubernetes_io_hostname=~\"^$Node$\"}[2m])) by (pod_name, kubernetes_io_hostname)", + "format": "time_series", + "interval": "10s", + "intervalFactor": 1, + "legendFormat": "<- {{ kubernetes_io_hostname }} | {{ pod_name }}", + "metric": "network", + "refId": "B", + "step": 60 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "All processes network I/O", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "allValue": "()", + "current": { + "selected": false, + "tags": [], + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Deployment", + "options": [], + "query": "label_values(deployment)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Statefulset", + "options": [], + "query": "label_values(statefulset)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Daemonset", + "options": [], + "query": "label_values(daemonset)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".*", + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "hide": 0, + "includeAll": true, + "label": null, + "multi": false, + "name": "Node", + "options": [], + "query": "label_values(kubernetes_io_hostname)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Deprecated - Deployment/Statefulset/Daemonset utilization metrics", + "uid": "deployment-metrics", + "version": 2 +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/aggregator-dashboard.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/aggregator-dashboard.json new file mode 100644 index 0000000000..e7c5b3691b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/aggregator-dashboard.json @@ -0,0 +1,668 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_fs_writes_bytes_total{pod=~\".+-aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage Write", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_fs_reads_bytes_total{pod=~\".+-aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage Read", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(container_memory_working_set_bytes{container=\"aggregator\",pod!=\"\",namespace=~\"$namespace\"} ) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate\r\n{container=\"aggregator\",pod!=\"\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{persistentvolumeclaim=~\"aggregator.+\",namespace=~\"$namespace\"}) by (namespace)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + } + ], + "title": "Storage Available", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(rate(container_network_receive_bytes_total{pod=~\".+aggregator-0\",namespace=~\"$namespace\"}[2m])) by (namespace)", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Network Receive Bytes", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{container=\"aggregator\"},namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(container_memory_working_set_bytes{container=\"aggregator\"},namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-1d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Aggregator Metrics", + "uid": "kubecost_aggregator_metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json new file mode 100644 index 0000000000..5c592b3399 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-container-stats.json @@ -0,0 +1,787 @@ +{ + "__inputs": [ + { + "name": "DS_THANOS", + "label": "Thanos", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.3.1" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Maximum CPU Core Usage vs avg Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "max(irate(container_cpu_usage_seconds_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\", container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {cluster_id=\"$cluster\",resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id,namespace,pod,container)", + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Max memory used vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "max(max_over_time(container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",cluster_id=\"$cluster\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "MEMORY_USAGE", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "avg(kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\",container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "refId": "MEMORY_REQUESTED" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {cluster_id=~\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_THANOS}" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",cluster_id=\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id,namespace,pod,container) (increase(container_cpu_cfs_periods_total{container!=\"\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" +], + "templating": { + "list": [ + { + "current": {}, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "definition": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "definition": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics (multi-cluster)", + "uid": "at-cost-analysis-pod2", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json new file mode 100644 index 0000000000..6dc0b153c8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-disk-usage.json @@ -0,0 +1,571 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.4.2" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_limit_bytes{instance=~'$disk', device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_limit_bytes{instance=~'$disk',device!=\"tmpfs\", id=\"/\", cluster_id=~'$cluster'}) by (cluster_id,instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}-{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "1 - sum(container_fs_inodes_free{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance) / sum(container_fs_inodes_total{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "iNode Utilization", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(container_fs_usage_bytes{instance=~'$disk',id=\"/\", cluster_id=~'$cluster'}) by (cluster_id, instance)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}}/{{instance}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk Usage", + "type": "timeseries" + } + ], + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "disk", + "options": [], + "query": { + "query": "label_values(container_fs_limit_bytes{cluster_id=~\"$cluster\"}, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Attached disk metrics (multi-cluster)", + "uid": "nBH7qBgMk", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json new file mode 100644 index 0000000000..40bf4e787b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/grafana-templates/multi-cluster-network-transfer-data.json @@ -0,0 +1,685 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.4.2" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 12, + "panels": [], + "title": "Network Data Transfers (negative is egress data)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 1 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(increase(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "All Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Internet Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 15 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\",namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\", sameRegion=\"false\",sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\",sameRegion=\"false\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Region Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 15 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Zone Data", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "kubecost" + ], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "pod_name", + "value": "pod_name" + } + ], + "query": "cluster_id, namespace, pod_name", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "name": "filter", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": {}, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Costs Metrics", + "uid": "kubecost-networkCosts-metrics", + "version": 8, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/kubernetes-resource-efficiency.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/kubernetes-resource-efficiency.json new file mode 100644 index 0000000000..156b3c292f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/kubernetes-resource-efficiency.json @@ -0,0 +1,408 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 29, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "Requests - Usage (negative values are unused reservations)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation) (\n (sum by (cluster_id,namespace,pod,container) (container_memory_usage_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n -(sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"memory\",unit=\"byte\",cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation) (\n -(sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"memory\",unit=\"byte\",cluster_id=~\"$cluster\",namespace=~\"$namespace\",container=~\"$container\",container!=\"POD\",container!=\"\"}))\n)", + "hide": true, + "legendFormat": "{{$aggregation}} Request", + "range": true, + "refId": "B" + } + ], + "title": "Memory Request-Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum by ($aggregation)(\n (sum by (cluster_id,namespace,pod,container) (rate(container_cpu_usage_seconds_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}[1h])))\n - \n (sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"cpu\",cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}))\n)\n \n", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum by ($aggregation)(\n (sum by (cluster_id,namespace,pod,container) (kube_pod_container_resource_requests{resource=\"cpu\",cluster_id=~\"$cluster\", namespace=~\"$namespace\", container=~\"$container\", container!=\"POD\",container!=\"\"}))\n)", + "hide": true, + "legendFormat": "{{$aggregation}} Request", + "range": true, + "refId": "B" + } + ], + "title": "CPU Request-Usage", + "type": "timeseries" + } + ], + "schemaVersion": 37, + "style": "dark", + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default", + "value": "default" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "container", + "value": "container" + } + ], + "query": "cluster_id,namespace,container", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels, cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels, cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=~\"$cluster\",namespace=~\"$namespace\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubernetes Resource Efficiency", + "uid": "kubernetes-resource-efficiency", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/label-cost-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/label-cost-utilization.json new file mode 100644 index 0000000000..dc1963edb3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/label-cost-utilization.json @@ -0,0 +1,1146 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "iteration": 1645115160709, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Monthly projected CPU cost given last 10m", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 0 + }, + "hideTimeOverride": true, + "id": 15, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n avg(container_cpu_allocation) by (pod,node)\n\n * on (node) group_left()\n avg(avg_over_time(node_cpu_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "CPU Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Based on CPU usage over last 24 hours", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 0 + }, + "hideTimeOverride": true, + "id": 16, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n avg(container_memory_allocation_bytes) by (pod,node) / 1024 / 1024 / 1024\n\n * on (node) group_left()\n avg(avg_over_time(node_ram_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Memory Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 0 + }, + "hideTimeOverride": true, + "id": 21, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "sum(\n sum(kube_persistentvolumeclaim_info{storageclass!=\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .04 \n\n+\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .17 \n", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Storage Cost", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cost of memory + CPU usage", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "currencyUSD" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 0 + }, + "hideTimeOverride": true, + "id": 20, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P0C970EB638C812D0" + }, + "exemplar": false, + "expr": "# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CPU ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsum(\n avg(container_cpu_allocation) by (pod,node)\n\n * on (node) group_left()\n avg(avg_over_time(node_cpu_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730\n\n#END CPU\n+\n\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Memory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nsum(\n avg(container_memory_allocation_bytes) by (pod,node) / 1024 / 1024 / 1024\n\n * on (node) group_left()\n avg(avg_over_time(node_ram_hourly_cost[10m])) by (node)\n\n * on (pod) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n )\n) * 730\n\n# END MEMORY\n\n+\n\n# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ STORAGE ~~~~~~~~~~~~~~~~~~~~~~~~~\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass!=\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .04 \n\n+\n\nsum(\n sum(kube_persistentvolumeclaim_info{storageclass=~\".*ssd.*\"}) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) / 1024 / 1024 /1024 * .17 \n\n\n# END STORAGE\n", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": " {{ node }}", + "refId": "A" + } + ], + "timeFrom": "15m", + "title": "Total Cost", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 5 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (pod)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n) ", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "CPU Request", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 3, + "y": 5 + }, + "id": 17, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",container_name!=\"POD\"}[1h])) by (kubernetes_io_hostname,pod_name),\n \"node\",\n \"$1\", \n \"kubernetes_io_hostname\", \n \"(.+)\"\n ) \n * on (pod_name) group_left()\n label_replace(\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod),\n \"pod_name\",\n \"$1\", \n \"pod\", \n \"(.+)\"\n ) or up * 0\n) ", + "format": "time_series", + "intervalFactor": 2, + "refId": "A" + } + ], + "title": "CPU Used", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 6, + "y": 5 + }, + "id": 11, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n sum (kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) by (pod)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n) ", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Memory Request", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 9, + "y": 5 + }, + "id": 18, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (container_memory_working_set_bytes{pod_name!=\"\",container!=\"POD\",container!=\"\"}) by (pod_name),\n \"pod\",\n \"$1\", \n \"pod_name\", \n \"(.+)\")\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod)\n or up * 0\n)", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Memory Usage", + "type": "stat" + }, + { + "datasource": { + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 22, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "expr": "sum(\n max(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, storageclass)\n * on (persistentvolumeclaim) group_right(storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes) by (persistentvolumeclaim)\n * on (persistentvolumeclaim) group_left(label_app)\n max(kube_persistentvolumeclaim_labels{label_$label=~\"$label_value\"}) by (persistentvolumeclaim) or up * 0\n) \n", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "title": "Storage Request", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_limits{resource=\"cpu\", unit=\"core\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limit", + "refId": "C" + }, + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "request", + "refId": "B" + }, + { + "expr": "sum(\n label_replace(\n sum (rate (container_cpu_usage_seconds_total{image!=\"\",container!=\"POD\",container!=\"\"}[10m])) by (container,pod),\n \"pod\", \n \"$1\", \n \"pod_name\", \n \"(.+)\"\n )\n * on (pod) group_left (label_$label)\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container)\n)\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU Usage vs Requests vs Limits", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "uid": "${datasource}" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_limits{resource=\"memory\", unit=\"byte\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "limit", + "refId": "C" + }, + { + "expr": "sum(\n label_replace(\n sum (kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\"}) by (pod, container)\n * on (pod) group_left()\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container),\n \"container_name\",\n \"$1\", \n \"container\", \n \"(.+)\"\n )\n) \n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "request", + "refId": "B" + }, + { + "expr": "sum(\n label_replace(\n sum (container_memory_working_set_bytes{container!=\"\",container!=\"POD\"}) by (container,pod),\n \"pod\", \n \"$1\", \n \"pod_name\", \n \"(.+)\"\n )\n * on (pod) group_left (label_$label)\n max(kube_pod_labels{label_$label=~\"$label_value\"}) by (pod,container)\n)\n", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "usage", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Memory Usage vs Requests vs Limits", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": false, + "schemaVersion": 34, + "style": "dark", + "tags": [ + "kubecost", + "cost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "label": "", + "name": "Filters", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": { + "tags": [], + "text": "app", + "value": "app" + }, + "hide": 0, + "includeAll": false, + "label": "Label", + "multi": false, + "name": "label", + "options": [ + { + "selected": true, + "text": "app", + "value": "app" + }, + { + "selected": false, + "text": "tier", + "value": "tier" + }, + { + "selected": false, + "text": "component", + "value": "component" + }, + { + "selected": false, + "text": "release", + "value": "release" + }, + { + "selected": false, + "text": "name", + "value": "name" + }, + { + "selected": false, + "text": "team", + "value": "team" + }, + { + "selected": false, + "text": "department", + "value": "department" + }, + { + "selected": false, + "text": "owner", + "value": "owner" + }, + { + "selected": false, + "text": "contact", + "value": "contact" + } + ], + "query": "app, tier, component, release, name, team, department, owner, contact", + "skipUrlSync": false, + "type": "custom" + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "Value", + "multi": false, + "name": "label_value", + "options": [], + "query": { + "query": "query_result(SUM(kube_pod_labels{label_$label!=\"\",namespace!=\"kube-system\"}) by (label_$label))", + "refId": "default-kubecost-label_value-Variable-Query" + }, + "refresh": 1, + "regex": "/label_$label=\\\"(.*?)(\\\")/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "()", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "Deployments", + "options": [], + "query": { + "query": "label_values(deployment)", + "refId": "default-kubecost-Deployments-Variable-Query" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "Secondary", + "options": [], + "query": { + "query": "query_result(kube_pod_labels)", + "refId": "default-kubecost-Secondary-Variable-Query" + }, + "refresh": 1, + "regex": "/.+?label_([^=]*).*/", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Label costs & utilization", + "uid": "lWMhIA-ik", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/namespace-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/namespace-utilization.json new file mode 100644 index 0000000000..a2e60c1f20 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/namespace-utilization.json @@ -0,0 +1,1175 @@ +{ + "annotations":{ + "list":[ + { + "builtIn":1, + "datasource":"-- Grafana --", + "enable":true, + "hide":true, + "iconColor":"rgba(0, 211, 255, 1)", + "name":"Annotations & Alerts", + "type":"dashboard" + } + ] + }, + "description":"A dashboard to help with utilization and resource allocation", + "editable":true, + "gnetId":8673, + "graphTooltip":0, + "id":9, + "iteration":1553150922105, + "links":[ + + ], + "panels":[ + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":9, + "w":16, + "x":0, + "y":0 + }, + "hideTimeOverride":true, + "id":73, + "links":[ + + ], + "pageSize":8, + "repeat":null, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":2, + "desc":false + }, + "styles":[ + { + "alias":"Pod", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "link":false, + "linkTooltip":"", + "linkUrl":"", + "pattern":"pod_name", + "thresholds":[ + "30", + "80" + ], + "type":"string", + "unit":"currencyUSD" + }, + { + "alias":"RAM", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "pattern":"Value #B", + "thresholds":[ + + ], + "type":"number", + "unit":"decbytes" + }, + { + "alias":"CPU %", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #A", + "thresholds":[ + + ], + "type":"number", + "unit":"percent" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"Storage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #C", + "thresholds":[ + + ], + "type":"number", + "unit":"currencyUSD" + }, + { + "alias":"Total", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #D", + "thresholds":[ + + ], + "type":"number", + "unit":"currencyUSD" + }, + { + "alias":"CPU Utilization", + "colorMode":"value", + "colors":[ + "#bf1b00", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #E", + "thresholds":[ + "30", + "80" + ], + "type":"number", + "unit":"percent" + }, + { + "alias":"RAM Utilization", + "colorMode":"value", + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#ef843c" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #F", + "thresholds":[ + "30", + "80" + ], + "type":"number", + "unit":"percent" + } + ], + "targets":[ + { + "expr":"sum (rate (container_cpu_usage_seconds_total{namespace=\"$namespace\"}[10m])) by (pod_name) * 100", + "format":"table", + "hide":false, + "instant":true, + "interval":"", + "intervalFactor":1, + "legendFormat":"{{ pod_name }}", + "refId":"A" + }, + { + "expr":"sum (avg_over_time (container_memory_working_set_bytes{namespace=\"$namespace\", container_name!=\"POD\"}[10m])) by (pod_name)", + "format":"table", + "hide":false, + "instant":true, + "intervalFactor":1, + "legendFormat":"{{ pod_name }}", + "refId":"B" + } + ], + "timeFrom":"1M", + "timeShift":null, + "title":"Pod utilization analysis", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":9, + "w":8, + "x":16, + "y":0 + }, + "hideTimeOverride":true, + "id":90, + "links":[ + + ], + "pageSize":8, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":4, + "desc":true + }, + "styles":[ + { + "alias":"Namespace", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"namespace", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"PVC Name", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"persistentvolumeclaim", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Storage Class", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"storageclass", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Size", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":1, + "mappingType":1, + "pattern":"Value", + "thresholds":[ + + ], + "type":"number", + "unit":"gbytes" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + } + ], + "targets":[ + { + "expr":"sum (\n sum(kube_persistentvolumeclaim_info) by (persistentvolumeclaim, namespace, storageclass)\n + on (persistentvolumeclaim, namespace) group_right (storageclass)\n sum(kube_persistentvolumeclaim_resource_requests_storage_bytes{namespace=~\"$namespace\"}) by (persistentvolumeclaim, namespace)\n) by (namespace,persistentvolumeclaim,storageclass) / 1024 / 1024 /1024 ", + "format":"table", + "hide":false, + "instant":true, + "interval":"", + "intervalFactor":1, + "legendFormat":"{{ persistentvolumeclaim }}", + "refId":"A" + } + ], + "timeFrom":null, + "timeShift":null, + "title":"Persistent Volume Claims", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "description":"CPU requests by pod divided by the rate of CPU usage over the last hour", + "fill":1, + "gridPos":{ + "h":9, + "w":24, + "x":0, + "y":9 + }, + "id":100, + "legend":{ + "avg":false, + "current":false, + "max":false, + "min":false, + "show":true, + "total":false, + "values":false + }, + "lines":true, + "linewidth":1, + "links":[ + + ], + "nullPointMode":"null", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"topk(10,\n label_replace(\n sum(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", namespace=\"$namespace\"}) by (pod),\n \"pod_name\", \n \"$1\", \n \"pod\", \n \"(.+)\"\n ) \n/ on (pod_name) sum(rate(container_cpu_usage_seconds_total{namespace=\"$namespace\",pod_name=~\".+\"}[1h])) by (pod_name))", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":[ + + ], + "timeFrom":null, + "timeShift":null, + "title":"Ratio of CPU requests to usage (Top 10 pods)", + "tooltip":{ + "shared":true, + "sort":0, + "value_type":"individual" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":true + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":3, + "description":"This panel shows historical utilization as an average across all pods in this namespace. It only accounts for currently deployed pods", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":18 + }, + "height":"", + "id":94, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum (rate (container_cpu_usage_seconds_total{namespace=\"$namespace\"}[10m])) by (namespace)\n", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"cpu utilization", + "metric":"container_cpu", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Overall CPU Utilization", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percent", + "label":"", + "logBase":1, + "max":"110", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"This panel shows historical utilization as an average across all pods in this namespace. It only accounts for currently deployed pods", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":18 + }, + "id":92, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":200, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum (container_memory_working_set_bytes{namespace=\"$namespace\"})\n/\nsum(node_memory_MemTotal_bytes)", + "format":"time_series", + "instant":false, + "intervalFactor":1, + "legendFormat":"mem utilization", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Overall RAM Utilization", + "tooltip":{ + "msResolution":false, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percent", + "label":null, + "logBase":1, + "max":"110", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Traffic in and out of this namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":24 + }, + "height":"", + "id":96, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_network_receive_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- in", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_network_transmit_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> out", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Network IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Disk reads and writes for the namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":24 + }, + "height":"", + "id":98, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_fs_writes_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- write", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_fs_reads_bytes_total{namespace=\"$namespace\"}[10m])) by (namespace)", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> read", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Disk IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + } + ], + "refresh":"10s", + "schemaVersion":16, + "style":"dark", + "tags":[ + "cost", + "utilization", + "metrics" + ], + "templating":{ + "list":[ + { + "current":{ + "text":"23.06", + "value":"23.06" + }, + "hide":0, + "label":"CPU", + "name":"costcpu", + "options":[ + { + "text":"23.06", + "value":"23.06" + } + ], + "query":"23.06", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"7.28", + "value":"7.28" + }, + "hide":0, + "label":"PE CPU", + "name":"costpcpu", + "options":[ + { + "text":"7.28", + "value":"7.28" + } + ], + "query":"7.28", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"3.25", + "value":"3.25" + }, + "hide":0, + "label":"RAM", + "name":"costram", + "options":[ + { + "text":"3.25", + "value":"3.25" + } + ], + "query":"3.25", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"0.6862", + "value":"0.6862" + }, + "hide":0, + "label":"PE RAM", + "name":"costpram", + "options":[ + { + "text":"0.6862", + "value":"0.6862" + } + ], + "query":"0.6862", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"0.04", + "value":"0.04" + }, + "hide":0, + "label":"Storage", + "name":"costStorageStandard", + "options":[ + { + "text":"0.04", + "value":"0.04" + } + ], + "query":"0.04", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":".17", + "value":".17" + }, + "hide":0, + "label":"SSD", + "name":"costStorageSSD", + "options":[ + { + "text":".17", + "value":".17" + } + ], + "query":".17", + "skipUrlSync":false, + "type":"constant" + }, + { + "current":{ + "text":"30", + "value":"30" + }, + "hide":0, + "label":"Disc.", + "name":"costDiscount", + "options":[ + { + "text":"30", + "value":"30" + } + ], + "query":"30", + "skipUrlSync":false, + "type":"constant" + }, + { + "allValue":null, + "current":{ + "text":"kube-system", + "value":"kube-system" + }, + "datasource":"${datasource}", + "hide":0, + "includeAll":false, + "label":"NS", + "multi":false, + "name":"namespace", + "options":[ + + ], + "query":"query_result(sum(kube_namespace_created{namespace!=\"\"}) by (namespace))", + "refresh":1, + "regex":"/namespace=\\\"(.*?)(\\\")/", + "skipUrlSync":false, + "sort":0, + "tagValuesQuery":"", + "tags":[ + + ], + "tagsQuery":"", + "type":"query", + "useTags":false + }, + { + "datasource":"${datasource}", + "filters":[ + + ], + "hide":0, + "label":"", + "name":"Filters", + "skipUrlSync":false, + "type":"adhoc" + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time":{ + "from":"now-15m", + "to":"now" + }, + "timepicker":{ + "hidden":false, + "refresh_intervals":[ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options":[ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone":"browser", + "title":"Namespace utilization metrics", + "uid":"at-cost-analysis-namespace2", + "version":1 +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/network-cloud-services.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/network-cloud-services.json new file mode 100644 index 0000000000..2729b6ca7d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/network-cloud-services.json @@ -0,0 +1,408 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 14, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) \nby (namespace,service) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{service}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) \nby(namespace, service) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{service}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Network Cloud Service by Namespace (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) )\nby(namespace, pod_name,service) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{pod_name}}/{{service}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\",service!=\"\",service=~\"$service\"}\n [1h]\n )\n) )\nby(namespace, pod_name,service) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{pod_name}}/{{service}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Network Cloud Service by Pod (egress is negative)", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values(kube_pod_container_status_running{namespace=\"$namespace\"},container)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_container_status_running{namespace=\"$namespace\"},container)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "All", + "value": "$__all" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Cloud Service Metrics", + "uid": "kubecost-network-cloud-services", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/networkCosts-metrics.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/networkCosts-metrics.json new file mode 100644 index 0000000000..79e568ccb1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/networkCosts-metrics.json @@ -0,0 +1,672 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 12, + "panels": [], + "title": "Network Data Transfers (negative is egress data)", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 1 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(increase(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n ))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "All Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]\n))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"true\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Internet Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 11, + "x": 0, + "y": 15 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\",namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\", sameRegion=\"false\",sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\",cluster_id=~\"$cluster\",pod_name=~\"$pod_name\",sameRegion=\"false\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation) ", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Region Data", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Cross region and cross zone subnets must be defined via the configMap. \nSee: \n", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 14, + "w": 13, + "x": 11, + "y": 15 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(increase(kubecost_pod_network_ingress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(increase(kubecost_pod_network_egress_bytes_total\n {internet=\"false\", namespace=~\"$namespace\", cluster_id=~\"$cluster\", pod_name=~\"$pod_name\", sameRegion=\"true\", sameZone=\"false\", service=~\"$service\"}\n [1h]))\nby($aggregation)", + "hide": false, + "interval": "1h", + "legendFormat": "__auto", + "range": true, + "refId": "B" + } + ], + "title": "Cross Zone Data", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "namespace", + "value": "namespace" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "aggregation", + "options": [ + { + "selected": false, + "text": "cluster_id", + "value": "cluster_id" + }, + { + "selected": true, + "text": "namespace", + "value": "namespace" + }, + { + "selected": false, + "text": "pod_name", + "value": "pod_name" + } + ], + "query": "cluster_id, namespace, pod_name", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=~\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod_name", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{cluster_id=~\"$cluster\", namespace=~\"$namespace\"},pod_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "filters": [], + "hide": 0, + "name": "filter", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "definition": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "service", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kubecost_pod_network_egress_bytes_total{namespace=~\"$namespace\"},service)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kubecost Network Costs Metrics", + "uid": "kubecost-networkCosts-metrics", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/node-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/node-utilization.json new file mode 100644 index 0000000000..dc03cc0743 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/node-utilization.json @@ -0,0 +1,1389 @@ +{ + "annotations":{ + "list":[ + { + "builtIn":1, + "datasource":"-- Grafana --", + "enable":true, + "hide":true, + "iconColor":"rgba(0, 211, 255, 1)", + "name":"Annotations & Alerts", + "type":"dashboard" + } + ] + }, + "editable":true, + "gnetId":null, + "graphTooltip":0, + "id":6, + "iteration":1557245882378, + "links":[ + + ], + "panels":[ + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":0, + "y":0 + }, + "id":2, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(irate(container_cpu_usage_seconds_total{id=\"/\",instance=\"$node\"}[10m]))", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"CPU Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":8, + "y":0 + }, + "id":3, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"SUM(container_memory_usage_bytes{namespace!=\"\",instance=\"$node\"}) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Memory Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"percentunit", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":true, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":7, + "w":8, + "x":16, + "y":0 + }, + "id":4, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(container_fs_usage_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"}) /\nsum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Storage Usage", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "columns":[ + { + "text":"Avg", + "value":"avg" + } + ], + "datasource":"${datasource}", + "fontSize":"100%", + "gridPos":{ + "h":8, + "w":16, + "x":0, + "y":7 + }, + "hideTimeOverride":true, + "id":21, + "links":[ + + ], + "pageSize":8, + "repeat":null, + "repeatDirection":"v", + "scroll":true, + "showHeader":true, + "sort":{ + "col":4, + "desc":true + }, + "styles":[ + { + "alias":"Pod", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(50, 172, 45, 0.97)", + "#c15c17" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "link":false, + "linkTooltip":"", + "linkUrl":"", + "pattern":"pod_name", + "thresholds":[ + "30", + "80" + ], + "type":"string", + "unit":"currencyUSD" + }, + { + "alias":"", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Time", + "thresholds":[ + + ], + "type":"hidden", + "unit":"short" + }, + { + "alias":"CPU Usage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #C", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"CPU Request", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #A", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"CPU Limit", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #B", + "thresholds":[ + + ], + "type":"number", + "unit":"short" + }, + { + "alias":"Mem Usage", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #D", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + }, + { + "alias":"Mem Request", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #E", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + }, + { + "alias":"Mem Limit", + "colorMode":null, + "colors":[ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat":"YYYY-MM-DD HH:mm:ss", + "decimals":2, + "mappingType":1, + "pattern":"Value #F", + "thresholds":[ + + ], + "type":"number", + "unit":"bytes" + } + ], + "targets":[ + { + "expr":"sum(rate(container_cpu_usage_seconds_total{container_name!=\"\",container_name!=\"POD\",pod_name!=\"\",instance=\"$node\"}[24h])) by (pod_name)", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"C" + }, + { + "expr":"sum(label_replace(\nsum(avg_over_time(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod), \n\"pod_name\",\"$1\",\"pod\",\"(.+)\")\nor up * 0\n) by (pod_name)", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"A" + }, + { + "expr":"sum(avg_over_time(container_memory_usage_bytes{container_name!=\"\",container_name!=\"POD\",pod_name!=\"\",instance=\"$node\"}[24h])) by (pod_name)\n", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"D" + }, + { + "expr":"sum(label_replace(label_replace(\nsum(avg_over_time(kube_pod_container_resource_requests{resource=\"memory\", unit=\"byte\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod),\n\"container_name\",\"$1\",\"container\",\"(.+)\"), \"pod_name\",\"$1\",\"pod\",\"(.+)\")\nor up * 0\n) by (pod_name)\n", + "format":"table", + "instant":true, + "intervalFactor":1, + "refId":"E" + } + ], + "timeFrom":"1M", + "timeShift":null, + "title":"Current pods", + "transform":"table", + "transparent":false, + "type":"table" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "decimals":0, + "format":"none", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":16, + "y":7 + }, + "id":8, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(\n count(avg_over_time(kube_pod_container_resource_requests{resource=\"cpu\", unit=\"core\", container!=\"\",container!=\"POD\",node=\"$node\"}[24h])) by (pod)\n * on (pod) group_right()\n sum(kube_pod_container_status_running) by (pod)\n)", + "format":"time_series", + "instant":true, + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Pods Running", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"bytes", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":20, + "y":7 + }, + "id":18, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"sum(container_fs_limit_bytes{device=~\"^/dev/[sv]d[a-z][1-9]$\",id=\"/\",instance=\"$node\"})", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"Storage Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"none", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":16, + "y":11 + }, + "id":9, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"kube_node_status_capacity{resource=\"cpu\", unit=\"core\", node=\"$node\"}", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"CPU Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"avg" + }, + { + "cacheTimeout":null, + "colorBackground":false, + "colorValue":false, + "colors":[ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource":"${datasource}", + "format":"bytes", + "gauge":{ + "maxValue":100, + "minValue":0, + "show":false, + "thresholdLabels":false, + "thresholdMarkers":true + }, + "gridPos":{ + "h":4, + "w":4, + "x":20, + "y":11 + }, + "id":19, + "interval":null, + "links":[ + + ], + "mappingType":1, + "mappingTypes":[ + { + "name":"value to text", + "value":1 + }, + { + "name":"range to text", + "value":2 + } + ], + "maxDataPoints":100, + "nullPointMode":"connected", + "nullText":null, + "postfix":"", + "postfixFontSize":"50%", + "prefix":"", + "prefixFontSize":"50%", + "rangeMaps":[ + { + "from":"null", + "text":"N/A", + "to":"null" + } + ], + "sparkline":{ + "fillColor":"rgba(31, 118, 189, 0.18)", + "full":false, + "lineColor":"rgb(31, 120, 193)", + "show":false + }, + "tableColumn":"", + "targets":[ + { + "expr":"kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"}", + "format":"time_series", + "intervalFactor":1, + "refId":"A" + } + ], + "thresholds":"", + "title":"RAM Capacity", + "type":"singlestat", + "valueFontSize":"80%", + "valueMaps":[ + { + "op":"=", + "text":"N/A", + "value":"null" + } + ], + "valueName":"current" + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":3, + "description":"This panel shows historical utilization for the node.", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":15 + }, + "height":"", + "id":11, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"sum(irate(container_cpu_usage_seconds_total{id=\"/\",instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"cpu utilization", + "metric":"container_cpu", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"CPU Utilization", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percentunit", + "label":"", + "logBase":1, + "max":"1.1", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"This panel shows historical utilization for the node.", + "editable":true, + "error":false, + "fill":0, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":15 + }, + "id":13, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":false, + "current":false, + "max":false, + "min":false, + "rightSide":false, + "show":false, + "sideWidth":200, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":true, + "targets":[ + { + "expr":"SUM(container_memory_usage_bytes{namespace!=\"\",instance=\"$node\"}) / SUM(kube_node_status_capacity{resource=\"memory\", unit=\"byte\", node=\"$node\"})", + "format":"time_series", + "instant":false, + "interval":"10s", + "intervalFactor":1, + "legendFormat":"ram utilization", + "metric":"container_memory_usage:sort_desc", + "refId":"A", + "step":10 + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"RAM Utilization", + "tooltip":{ + "msResolution":false, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "decimals":null, + "format":"percentunit", + "label":null, + "logBase":1, + "max":"1.1", + "min":"0", + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Traffic in and out of this namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":0, + "y":21 + }, + "height":"", + "id":15, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_network_receive_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- in", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_network_transmit_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> out", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Network IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + }, + { + "aliasColors":{ + + }, + "bars":false, + "dashLength":10, + "dashes":false, + "datasource":"${datasource}", + "decimals":2, + "description":"Disk reads and writes for the namespace, as a sum of the pods within it", + "editable":true, + "error":false, + "fill":1, + "grid":{ + + }, + "gridPos":{ + "h":6, + "w":12, + "x":12, + "y":21 + }, + "height":"", + "id":17, + "isNew":true, + "legend":{ + "alignAsTable":false, + "avg":true, + "current":true, + "hideEmpty":false, + "hideZero":false, + "max":false, + "min":false, + "rightSide":false, + "show":true, + "sideWidth":null, + "sort":"current", + "sortDesc":true, + "total":false, + "values":true + }, + "lines":true, + "linewidth":2, + "links":[ + + ], + "nullPointMode":"connected", + "percentage":false, + "pointradius":5, + "points":false, + "renderer":"flot", + "seriesOverrides":[ + + ], + "spaceLength":10, + "stack":false, + "steppedLine":false, + "targets":[ + { + "expr":"sum (rate (container_fs_writes_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"<- write", + "metric":"container_cpu", + "refId":"A", + "step":10 + }, + { + "expr":"- sum (rate (container_fs_reads_bytes_total{instance=\"$node\"}[10m]))", + "format":"time_series", + "hide":false, + "instant":false, + "interval":"", + "intervalFactor":1, + "legendFormat":"-> read", + "refId":"B" + } + ], + "thresholds":[ + + ], + "timeFrom":"", + "timeShift":null, + "title":"Disk IO", + "tooltip":{ + "msResolution":true, + "shared":true, + "sort":2, + "value_type":"cumulative" + }, + "type":"graph", + "xaxis":{ + "buckets":null, + "mode":"time", + "name":null, + "show":true, + "values":[ + + ] + }, + "yaxes":[ + { + "format":"Bps", + "label":"", + "logBase":1, + "max":null, + "min":null, + "show":true + }, + { + "format":"short", + "label":null, + "logBase":1, + "max":null, + "min":null, + "show":false + } + ], + "yaxis":{ + "align":false, + "alignLevel":null + } + } + ], + "schemaVersion":16, + "style":"dark", + "tags":[ + "cost", + "utilization", + "metrics" + ], + "templating":{ + "list":[ + { + "allValue":null, + "current":{ + "text":"ip-172-20-44-170.us-east-2.compute.internal", + "value":"ip-172-20-44-170.us-east-2.compute.internal" + }, + "datasource":"${datasource}", + "hide":0, + "includeAll":false, + "label":null, + "multi":false, + "name":"node", + "options":[ + + ], + "query":"query_result(kube_node_labels)", + "refresh":1, + "regex":"/node=\\\"(.*?)(\\\")/", + "skipUrlSync":false, + "sort":0, + "tagValuesQuery":"", + "tags":[ + + ], + "tagsQuery":"", + "type":"query", + "useTags":false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time":{ + "from":"now-6h", + "to":"now" + }, + "timepicker":{ + "refresh_intervals":[ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options":[ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone":"", + "title":"Node utilization metrics", + "uid":"NUQW37Lmk", + "version":1 +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization-multi-cluster.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization-multi-cluster.json new file mode 100644 index 0000000000..3054b9fdde --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization-multi-cluster.json @@ -0,0 +1,788 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": 4, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Maximum CPU Core Usage vs avg Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "max(irate(container_cpu_usage_seconds_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\", container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {cluster_id=\"$cluster\",resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id,namespace,pod,container)", + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Max memory used vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "max(max_over_time(container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",cluster_id=\"$cluster\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "MEMORY_USAGE", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "avg(kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container=~\"$container\",container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}/{{container}} (requested)", + "refId": "MEMORY_REQUESTED" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {cluster_id=~\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {cluster_id=\"$cluster\",namespace=~\"$namespace\",container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",cluster_id=\"$cluster\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id,namespace,pod,container) (increase(container_cpu_cfs_periods_total{container!=\"\",cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 39, + "tags": [ + "utilization", + "metrics", + "kubecost" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "CostManagement", + "value": "CostManagement" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(cluster_id)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cluster_id)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels{cluster_id=\"$cluster\"}, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{cluster_id=\"$cluster\",namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "Thanos" + }, + "definition": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{cluster_id=\"$cluster\",namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics (multi-cluster)", + "uid": "at-cost-analysis-pod-multicluster", + "version": 2, + "weekStart": "" +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization.json new file mode 100644 index 0000000000..f037af45ea --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/pod-utilization.json @@ -0,0 +1,757 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 9063, + "graphTooltip": 0, + "id": 11, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Maximum CPU Core Usage vs Requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 94, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(irate(\r\n container_cpu_usage_seconds_total\r\n {namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\n by (cluster_id, namespace, pod, container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "avg(kube_pod_container_resource_requests\r\n {resource=\"cpu\",unit=\"core\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\"}\r\n ) \r\nby (cluster_id, namespace, pod, container)", + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (avg requested)", + "range": true, + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU Core Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Max Memory usage vs avg requested", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 96, + "links": [], + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "max(max_over_time(\r\n container_memory_working_set_bytes\r\n {namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\",container!=\"POD\",container!=\"\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (usage max)", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "avg(\n kube_pod_container_resource_requests\n {resource=\"memory\",unit=\"byte\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\", container!=\"POD\"}\n )\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}} (avg requested)", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Memory Usage vs Requested", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Network traffic by pod", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 95, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(irate(container_network_receive_bytes_total\n {namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}<- in", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_network_transmit_bytes_total\n {namespace=~\"$namespace\",pod=~\"$pod\"}\n [$__rate_interval])) \nby (cluster_id, namespace, pod)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{namespace}}/{{pod}}-> out", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Network IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "Disk read writes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 3600000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 97, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(irate(container_fs_writes_bytes_total\r\n {container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}<- write", + "metric": "container_cpu", + "refId": "A", + "step": 10 + }, + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "- sum(irate(container_fs_reads_bytes_total\r\n {container!=\"POD\",pod!=\"\",pod=~\"$pod\",container=~\"$container\"}\r\n [$__rate_interval])) \r\nby (cluster_id,namespace,pod,container)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{cluster_id}} {{pod}}/{{container}}-> read", + "refId": "B" + } + ], + "timeFrom": "", + "title": "Disk IO", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This graph shows the % of periods where a pod is being throttled. Values range from 0-100", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 1800000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 99, + "links": [], + "options": { + "legend": { + "calcs": [ + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "100\n * sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))\n / sum by(cluster_id, namespace, pod, container) (increase(container_cpu_cfs_periods_total{container!=\"\", namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", container!=\"POD\"}[$__rate_interval]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "__auto", + "refId": "B" + } + ], + "timeFrom": "", + "title": "CPU throttle percent", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "Prometheus" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels, namespace) ", + "hide": 0, + "includeAll": true, + "label": "", + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_labels, namespace) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_labels{namespace=~\"$namespace\"}, pod) ", + "hide": 0, + "includeAll": true, + "label": "pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_labels{namespace=~\"$namespace\"}, pod) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "query": "label_values(container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\", container!=\"POD\"}, container) ", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": { + "hidden": false, + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Pod utilization metrics", + "uid": "at-cost-analysis-pod", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/prom-benchmark.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/prom-benchmark.json new file mode 100644 index 0000000000..ff054acc2f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/prom-benchmark.json @@ -0,0 +1,5691 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Metrics useful for benchmarking and loadtesting Prometheus itself. Designed primarily for Prometheus 2.17.x.", + "editable": true, + "gnetId": 12054, + "graphTooltip": 1, + "id": 9, + "iteration": 1603144824023, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 49, + "panels": [], + "title": "Basics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 1 + }, + "hiddenSeries": false, + "id": 40, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_build_info{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{version}} - {{revision}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Prometheus Version", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 1 + }, + "hiddenSeries": false, + "id": 72, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "time() - process_start_time_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Age", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Uptime", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "dtdurations", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 1 + }, + "hiddenSeries": false, + "id": 107, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "time() - prometheus_config_last_reload_success_timestamp_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Age", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Last Successful Config Reload", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "dtdurations", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 46, + "panels": [], + "title": "Ingestion", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Time series": "#70dbed" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_series{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Time series", + "metric": "prometheus_local_storage_memory_series", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Time series", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 9 + }, + "hiddenSeries": false, + "id": 26, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_active_appenders{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Head Appenders", + "metric": "prometheus_local_storage_memory_series", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Active Appenders", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "samples/s": "#e5a8e2" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 9 + }, + "hiddenSeries": false, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_samples_appended_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "samples/s", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Samples Appended/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "To persist": "#9AC48A" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/Max.*/", + "fill": 0 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_chunks{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunks", + "metric": "prometheus_local_storage_memory_chunks", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Chunks", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 16 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_chunks_created_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Created", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_head_chunks_removed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Removed", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Chunks/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Removed": "#e5ac0e" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 16 + }, + "hiddenSeries": false, + "id": 25, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_isolation_high_watermark{job=\"prometheus\",instance=\"$Prometheus:9090\"} - prometheus_tsdb_isolation_low_watermark{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Difference", + "metric": "prometheus_local_storage_chunk_ops_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Isolation Watermarks", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 52, + "panels": [], + "title": "Compaction", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max": "#447ebc", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "Min": "#447ebc", + "Now": "#7eb26d" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Max", + "fillBelowTo": "Min", + "lines": false + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_head_min_time{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Min", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "time() * 1000", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "Now", + "refId": "C" + }, + { + "expr": "prometheus_tsdb_head_max_time{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Max", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head Time Range", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "dateTimeAsIso", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 24 + }, + "hiddenSeries": false, + "id": 29, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_head_gc_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "GC Time/s", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Head GC Time/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 24 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Queue length", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_blocks_loaded{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Blocks Loaded", + "metric": "prometheus_local_storage_indexing_batch_sizes_sum", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Blocks Loaded", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Failed Reloads": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 31 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_reloads_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Reloads", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TSDB Reloads/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 31 + }, + "hiddenSeries": false, + "id": 31, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_wal_fsync_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_tsdb_wal_fsync_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Fsync Latency", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_wal_truncate_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m]) / rate(prometheus_tsdb_wal_trunacte_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Truncate Latency", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "WAL Fsync&Truncate Latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "{instance=\"demo.robustperception.io:9090\",job=\"prometheus\"}": "#bf1b00" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 31 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_wal_corruptions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Corruptions", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_reloads_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Reload Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "B", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_head_series_not_found{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Head Series Not Found", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "C", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_compactions_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Compaction Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "D", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_retention_cutoffs_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Retention Cutoff Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "E", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_checkpoint_creations_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Checkpoint Creation Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "F", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_checkpoint_deletions_failed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "WAL Checkpoint Deletion Failures", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "G", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TSDB Problems/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Failed Compactions": "#bf1b00", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 38 + }, + "hiddenSeries": false, + "id": 19, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compactions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Compactions", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compactions/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 38 + }, + "hiddenSeries": false, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compaction Time/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Allocated bytes": "#F9BA8F", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#890F02" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 38 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_time_retentions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Time Cutoffs", + "metric": "last", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(prometheus_tsdb_size_retentions_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Size Cutoffs", + "metric": "last", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Retention Cutoffs/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 45 + }, + "hiddenSeries": false, + "id": 27, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_range_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_range_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunk Time Range", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Chunk Time Range", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "dtdurationms", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 45 + }, + "hiddenSeries": false, + "id": 35, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_size_bytes_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Bytes/Sample", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Bytes/Sample", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 45 + }, + "hiddenSeries": false, + "id": 34, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_tsdb_compaction_chunk_samples_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m]) / rate(prometheus_tsdb_compaction_chunk_samples_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[10m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Chunk Samples", + "metric": "prometheus_local_storage_series_chunks_persisted_count", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "First Compaction, Avg Chunk Samples", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 55, + "panels": [], + "title": "Resource Usage", + "type": "row" + }, + { + "aliasColors": { + "Allocated bytes": "#7EB26D", + "Allocated bytes - 1m max": "#BF1B00", + "Allocated bytes - 1m min": "#BF1B00", + "Allocated bytes - 5m max": "#BF1B00", + "Allocated bytes - 5m min": "#BF1B00", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#447EBC" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": null, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 53 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/-/", + "fill": 0 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_resident_memory_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "RSS", + "metric": "process_resident_memory_bytes", + "refId": "B", + "step": 10 + }, + { + "expr": "prometheus_local_storage_target_heap_size_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Target heap size", + "metric": "go_memstats_alloc_bytes", + "refId": "D", + "step": 10 + }, + { + "expr": "go_memstats_next_gc_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Next GC", + "metric": "go_memstats_next_gc_bytes", + "refId": "C", + "step": 10 + }, + { + "expr": "go_memstats_alloc_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "intervalFactor": 2, + "legendFormat": "Allocated", + "metric": "go_memstats_alloc_bytes", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Allocated bytes": "#F9BA8F", + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833", + "RSS": "#890F02" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 53 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(go_memstats_alloc_bytes_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Allocated Bytes/s", + "metric": "go_memstats_alloc_bytes", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Allocations", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 53 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(process_cpu_seconds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "intervalFactor": 2, + "legendFormat": "Irate", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + }, + { + "expr": "rate(process_cpu_seconds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "intervalFactor": 2, + "legendFormat": "5m rate", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 60 + }, + "hiddenSeries": false, + "id": 70, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_symbol_table_size_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "RAM Used", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Symbol Tables Size", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "decimals": 2, + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 60 + }, + "hiddenSeries": false, + "id": 71, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_tsdb_storage_blocks_bytes_total{job=\"prometheus\",instance=\"$Prometheus:9090\"} or prometheus_tsdb_storage_blocks_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Disk Used", + "metric": "prometheus_local_storage_ingested_samples_total", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Block Size", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ + "avg" + ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Max": "#e24d42", + "Open": "#508642" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 60 + }, + "hiddenSeries": false, + "id": 41, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_max_fds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Max", + "refId": "A" + }, + { + "expr": "process_open_fds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Open", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "File Descriptors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 67 + }, + "id": 91, + "panels": [], + "title": "Service Discovery", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 68 + }, + "hiddenSeries": false, + "id": 42, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_sd_discovered_targets{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}-{{config}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Discovered Targets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 68 + }, + "hiddenSeries": false, + "id": 96, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_sd_updates_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sent Updates/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 68 + }, + "hiddenSeries": false, + "id": 97, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_sd_received_updates_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[5m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Received Updates/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 99, + "panels": [], + "title": "Scraping", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 76 + }, + "hiddenSeries": false, + "id": 105, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_target_interval_length_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_target_interval_length_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{interval}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Scrape Interval", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 76 + }, + "hiddenSeries": false, + "id": 104, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_target_scrapes_exceeded_sample_limit_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Exceeded Sample Limit", + "refId": "A" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_duplicate_timestamp_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Duplicate Timestamp", + "refId": "C" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_out_of_bounds_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Out Of Bounds ", + "refId": "D" + }, + { + "expr": "rate(prometheus_target_scrapes_sample_out_of_order_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Out of Order", + "refId": "E" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Scrape Problems/s", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 76 + }, + "hiddenSeries": false, + "id": 95, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_target_metadata_cache_bytes{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{scrape_job}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Metadata Cache Size", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 83 + }, + "id": 63, + "panels": [], + "title": "Query Engine", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "Time spent in each mode, per second", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 84 + }, + "hiddenSeries": false, + "id": 24, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_engine_query_duration_seconds_sum{job=\"prometheus\",}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{slice}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Query engine timings/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 84 + }, + "hiddenSeries": false, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_rule_group_iterations_missed_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) ", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Rule group missed", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + }, + { + "expr": "rate(prometheus_rule_evaluation_failures_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rule evals failed", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "C", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rule group evaulation problems/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 84 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_rule_group_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Rule evaluation duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Evaluation time of rule groups/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 91 + }, + "id": 77, + "panels": [ + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 92 + }, + "hiddenSeries": false, + "id": 86, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_sent_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Sent/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 92 + }, + "hiddenSeries": false, + "id": 87, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_errors_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_notifications_sent_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Error Ratio", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 92 + }, + "hiddenSeries": false, + "id": 81, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_latency_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m]) / rate(prometheus_notifications_latency_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{alertmanager}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 99 + }, + "hiddenSeries": false, + "id": 85, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_notifications_alertmanagers_discovered{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Alertmanagers", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Alertmanagers Discovered", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 99 + }, + "hiddenSeries": false, + "id": 89, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_notifications_dropped_total{job=\"prometheus\",instance=\"$Prometheus:9090\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Dropped", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notifications Dropped/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 99 + }, + "hiddenSeries": false, + "id": 88, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_notifications_queue_length{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Pending", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_notifications_queue_capacity{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Max", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notification Queue", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Notification", + "type": "row" + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 92 + }, + "id": 58, + "panels": [], + "title": "HTTP Server", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 93 + }, + "hiddenSeries": false, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP requests/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 93 + }, + "hiddenSeries": false, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m]) / rate(prometheus_http_request_duration_seconds_count{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP request latency", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "description": "", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 93 + }, + "hiddenSeries": false, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "rate(prometheus_http_request_duration_seconds_sum{job=\"prometheus\",instance=\"$Prometheus:9090\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{handler}}", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Time spent in HTTP requests/s", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 100 + }, + "id": 61, + "panels": [], + "repeat": "RuleGroup", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "title": "Rule Group: $RuleGroup", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 101 + }, + "hiddenSeries": false, + "id": 43, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatDirection": "h", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_interval_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Interval", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Last Duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Duration", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 101 + }, + "hiddenSeries": false, + "id": 66, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;CPU", + "value": "/etc/config/rules;CPU" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_rules{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rules", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Rules", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${datasource}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 108 + }, + "id": 108, + "panels": [], + "repeat": null, + "repeatIteration": 1603144824023, + "repeatPanelId": 61, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "title": "Rule Group: $RuleGroup", + "type": "row" + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 109 + }, + "hiddenSeries": false, + "id": 109, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatDirection": "h", + "repeatIteration": 1603144824023, + "repeatPanelId": 43, + "repeatedByRow": true, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_interval_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Interval", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + }, + { + "expr": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Last Duration", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "B", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Duration", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Chunks": "#1F78C1", + "Chunks to persist": "#508642", + "Interval": "#890f02", + "Last Duration": "#f9934e", + "Max chunks": "#052B51", + "Max to persist": "#3F6833" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${datasource}", + "editable": true, + "error": false, + "fieldConfig": { + "defaults": { + "custom": {}, + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 109 + }, + "hiddenSeries": false, + "id": 110, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.1", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1603144824023, + "repeatPanelId": 66, + "repeatedByRow": true, + "scopedVars": { + "RuleGroup": { + "selected": false, + "text": "/etc/config/rules;Savings", + "value": "/etc/config/rules;Savings" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "prometheus_rule_group_rules{job=\"prometheus\",instance=\"$Prometheus:9090\",rule_group=~\"$RuleGroup\"}\n", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Rules", + "metric": "prometheus_local_storage_memory_chunkdescs", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$RuleGroup: Rules", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 26, + "style": "dark", + "tags": [ + "kubecost" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": false, + "text": "localhost", + "value": "localhost" + }, + "datasource": "${datasource}", + "definition": "", + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "Prometheus", + "options": [], + "query": "query_result(up{job=\"prometheus\"} == 1)", + "refresh": 2, + "regex": ".*instance=\"([^\"]+):9090\".*", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": null, + "tags": [], + "tagsQuery": null, + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": "${datasource}", + "definition": "", + "hide": 2, + "includeAll": true, + "label": null, + "multi": false, + "name": "RuleGroup", + "options": [], + "query": "prometheus_rule_group_last_duration_seconds{job=\"prometheus\",instance=\"$Prometheus:9090\"}", + "refresh": 2, + "regex": ".*rule_group=\"(.*?)\".*", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": { + "selected": true, + "text": "default-kubecost", + "value": "default-kubecost" + }, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "utc", + "title": "Prometheus Benchmark - 2.17.x", + "uid": "L0HBvojWz", + "version": 4 +} diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics-aggregator.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics-aggregator.json new file mode 100644 index 0000000000..660358905e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics-aggregator.json @@ -0,0 +1,988 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 7, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5,sum(container_memory_working_set_bytes{container=\"aggregator\",namespace=~\"$namespace\"} ) by (namespace,pod,container))", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5, (\r\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",namespace=~\"$namespace\",container=\"aggregator\"}[$__rate_interval])) by (namespace,pod,container)\r\n )\r\n)", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Top Network (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(topk(5,rate(container_network_receive_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "receive" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(topk(5,rate(container_network_transmit_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "transmit" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "container_network_transmit_bytes_total{}", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Top Network (transmit is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "\n sum(rate(container_fs_writes_bytes_total\n {container=\"aggregator\",namespace=~\"$namespace\",image!=\"\"}\n [$__rate_interval]))\n by (namespace,pod,container)\n>0 ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_write" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-(sum\r\n (rate(container_fs_reads_bytes_total\r\n {container=\"aggregator\",namespace=~\"$namespace\",image!=\"\"}\r\n [$__rate_interval])) \r\nby (namespace,pod,container) \r\n) <0", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_read" + } + ], + "title": "Storage (read is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This may work depending on the CRI", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{namespace=~\"$namespace\"}) by (namespace,persistentvolumeclaim)", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{persistentvolumeclaim}}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kube_persistentvolume_capacity_bytes) by (persistentvolume)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 82, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(kube_pod_container_status_restarts_total{namespace=~\"$namespace\",pod=~\".+-aggregator-0\"}[1h])) by (namespace,container)>0", + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Pod restarts per hour", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "kubecost_read_db_size", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "kubecost_read_db_size" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubecost_write_db_size)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "kubecost_write_db_size" + } + ], + "title": "Aggregator DB Size", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Workload Metrics - Aggregator", + "uid": "kubecost-aggregator-metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics.json b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics.json new file mode 100644 index 0000000000..248afc1348 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/grafana-dashboards/workload-metrics.json @@ -0,0 +1,893 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Most used metrics when troubleshooting applications", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5,sum(container_memory_working_set_bytes{container=~\"$container\",pod=~\"$pod\",container!=\"\",namespace=~\"$namespace\"} ) by (namespace,pod,container))", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top Memory Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "topk(5, (\r\n sum(rate(container_cpu_usage_seconds_total{image!=\"\",namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$container\"}[10m])) by (namespace,pod,container)\r\n )\r\n)", + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Top CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(topk(5,\n rate(kubecost_pod_network_ingress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "- sum(topk(5,\n rate(kubecost_pod_network_egress_bytes_total\n {namespace=~\"$namespace\", pod_name=~\"$pod\"}\n [$__rate_interval]\n )\n) )\nby(namespace, pod_name) ", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{namespace}}/{{pod_name}}", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Kubecost Top Network (egress is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "smooth", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(topk(5,rate(container_network_receive_bytes_total{pod=~\"$pod\",namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "receive" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-sum(topk(5,rate(container_network_transmit_bytes_total{pod=~\"$pod\",namespace=~\"$namespace\"}[$__rate_interval]))) by (namespace,pod) ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{pod}}", + "range": true, + "refId": "transmit" + } + ], + "title": "Top Network (transmit is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 3600000, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "\n sum(rate(container_fs_writes_bytes_total\n {pod=~\"$pod\",namespace=~\"$namespace\",image!=\"\"}\n [$__rate_interval]))\n by (namespace,pod,container)\n>0 ", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_write" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "-(sum\r\n (rate(container_fs_reads_bytes_total\r\n {pod=~\"$pod\",namespace=~\"$namespace\",image!=\"\"}\r\n [$__rate_interval])) \r\nby (namespace,pod,container) \r\n) <0", + "hide": false, + "instant": false, + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "storage_read" + } + ], + "title": "Storage (read is negative)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "This may work depending on the CRI", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kubelet_volume_stats_available_bytes{namespace=~\"$namespace\"}) by (namespace,persistentvolumeclaim)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "expr": "sum(kube_persistentvolume_capacity_bytes) by (persistentvolume)", + "hide": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Storage ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 82, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(kube_pod_container_status_restarts_total{namespace=~\"$namespace\",pod=~\"$pod\"}[1h])) by (namespace,container)>0", + "instant": false, + "interval": "1h", + "legendFormat": "{{namespace}}/{{container}}", + "range": true, + "refId": "A" + } + ], + "title": "Pod restarts per hour", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "kubecost", + "utilization", + "metrics" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "PBFA97CFB590B2093" + }, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "kubecost", + "value": "kubecost" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_namespace_labels,namespace)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_namespace_labels,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "pod", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(kube_pod_owner{namespace=~\"$namespace\"},pod)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "definition": "label_values({namespace=~\"$namespace\", pod=~\"$pod\"},container)", + "hide": 0, + "includeAll": true, + "multi": false, + "name": "container", + "options": [], + "query": { + "qryType": 1, + "query": "label_values({namespace=~\"$namespace\", pod=~\"$pod\"},container)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 5, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Workload Metrics", + "uid": "kubecost-workload-metrics", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/questions.yaml b/charts/kubecost/cost-analyzer/2.3.5/questions.yaml new file mode 100644 index 0000000000..7717d04dc4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/questions.yaml @@ -0,0 +1,187 @@ +questions: + # General Settings + - variable: kubecostProductConfigs.clusterName + label: Cluster Name + description: "Used for display in the cost-analyzer UI (Can be renamed in the UI)" + type: string + required: true + default: "" + group: General Settings + - variable: persistentVolume.enabled + label: Enable Persistent Volume for CostAnalyzer + description: "If true, Kubecost will create a Persistent Volume Claim for product config data" + type: boolean + default: false + show_subquestion_if: true + group: "General Settings" + subquestions: + - variable: persistentVolume.size + label: CostAnalyzer Persistent Volume Size + type: string + default: "0.2Gi" + # Amazon EKS + - variable: AmazonEKS.enabled + label: Amazon EKS cluster + description: "If true, Kubecost will be installed with the images and helm chart from https://gallery.ecr.aws/kubecost/" + type: boolean + default: false + show_subquestion_if: true + group: General Settings + subquestions: + - variable: kubecostFrontend.image + label: Kubecost frontend image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/frontend" + type: string + default: "" + - variable: kubecostModel.image + label: Kubecost cost-model image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/cost-model" + type: string + default: "" + - variable: prometheus.server.image.repository + label: Kubecost Prometheus image for Amazon EKS + description: "Use this image for the Amazon EKS cluster: public.ecr.aws/kubecost/prometheus" + type: string + default: "" + - variable: prometheus.server.image.tag + label: Kubecost Prometheus image tag for Amazon EKS + type: string + default: "v2.35.0" + # Prometheus Server + - variable: global.prometheus.enabled + label: Enable Prometheus + description: If false, use an existing Prometheus install + type: boolean + default: true + group: "Prometheus" + - variable: prometheus.kubeStateMetrics.enabled + label: Enable KubeStateMetrics + description: "If true, deploy kube-state-metrics for Kubernetes metrics" + type: boolean + default: true + show_if: "global.prometheus.enabled=true" + group: "Prometheus" + - variable: prometheus.server.retention + label: Prometheus Server Retention + description: "Determines when to remove old data" + type: string + default: "15d" + show_if: "global.prometheus.enabled=true" + group: "Prometheus" + - variable: prometheus.server.persistentVolume.enabled + label: Create Persistent Volume for Prometheus + description: "If true, prometheus will create a persistent volume claim" + type: boolean + required: true + default: false + group: "Prometheus" + show_if: "global.prometheus.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.server.persistentVolume.size + label: Prometheus Persistent Volume Size + type: string + default: "8Gi" + - variable: prometheus.server.persistentVolume.storageClass + label: Prometheus Persistent Volume StorageClass + description: "Prometheus data persistent volume storageClass, if not set use default StorageClass" + default: "" + type: storageclass + - variable: prometheus.server.persistentVolume.existingClaim + label: Existing Persistent Volume Claim for Prometheus + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + default: "" + + # Prometheus Node Exporter + - variable: prometheus.nodeExporter.enabled + label: Enable NodeExporter + description: "If false, do not create NodeExporter daemonset" + type: boolean + default: true + group: "NodeExporter" + - variable: prometheus.serviceAccounts.nodeExporter.create + label: Enable Service Accounts NodeExporter + description: "If false, do not create NodeExporter daemonset" + type: boolean + default: true + group: "NodeExporter" + + # Prometheus AlertManager + - variable: prometheus.alertmanager.enabled + label: Enable AlertManager + type: boolean + default: false + group: "AlertManager" + - variable: prometheus.alertmanager.persistentVolume.enabled + label: Create Persistent Volume for AlertManager + description: "If true, alertmanager will create a persistent volume claim" + type: boolean + required: true + default: false + group: "AlertManager" + show_if: "prometheus.alertmanager.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.alertmanager.persistentVolume.size + default: "2Gi" + description: "AlertManager data persistent volume size" + type: string + label: AlertManager Persistent Volume Size + - variable: prometheus.alertmanager.persistentVolume.storageClass + default: "" + description: "Alertmanager data persistent volume storageClass, if not set use default StorageClass" + type: storageclass + label: AlertManager Persistent Volume StorageClass + - variable: prometheus.alertmanager.persistentVolume.existingClaim + default: "" + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + label: Existing Persistent Volume Claim for AlertManager + + # PushGateway + - variable: prometheus.pushgateway.enabled + label: Enable PushGateway + type: boolean + default: false + group: "PushGateway" + - variable: prometheus.pushgateway.persistentVolume.enabled + label: Create Persistent Volume for PushGateway + description: "If true, PushGateway will create a persistent volume claim" + required: true + type: boolean + default: false + group: "PushGateway" + show_if: "prometheus.pushgateway.enabled=true" + show_subquestion_if: true + subquestions: + - variable: prometheus.prometheus.pushgateway.persistentVolume.size + label: PushGateway Persistent Volume Size + type: string + default: "2Gi" + - variable: prometheus.pushgateway.persistentVolume.storageClass + label: PushGateway Persistent Volume StorageClass + description: "PushGateway data persistent volume storageClass, if not set use default StorageClass" + type: storageclass + default: "" + - variable: prometheus.pushgateway.persistentVolume.existingClaim + label: Existing Persistent Volume Claim for PushGateway + description: "If not empty, uses the specified existing PVC instead of creating new one" + type: pvc + default: "" + + # Services and Load Balancing + - variable: ingress.enabled + label: Enable Ingress + description: "Expose app using Ingress (Layer 7 Load Balancer)" + default: false + type: boolean + show_subquestion_if: true + group: "Services and Load Balancing" + subquestions: + - variable: ingress.hosts[0] + default: "xip.io" + description: "Hostname to your CostAnalyzer installation" + type: hostname + required: true + label: Hostname diff --git a/charts/kubecost/cost-analyzer/2.3.5/scripts/create-admission-controller-tls.sh b/charts/kubecost/cost-analyzer/2.3.5/scripts/create-admission-controller-tls.sh new file mode 100644 index 0000000000..2290cadd1b --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/scripts/create-admission-controller-tls.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eo pipefail + +if [ -z "$1" ]; then + namespace=kubecost +else + namespace="$1" +fi + +echo -e "\nCreating certificates ..." +mkdir certs +openssl genrsa -out certs/tls.key 2048 +openssl req -new -key certs/tls.key -out certs/tls.csr -subj "/CN=webhook-server.${namespace}.svc" +openssl x509 -req -days 500 -extfile <(printf "subjectAltName=DNS:webhook-server.%s.svc" "${namespace}") -in certs/tls.csr -signkey certs/tls.key -out certs/tls.crt + +echo -e "\nCreating Webhook Server TLS Secret ..." +kubectl create secret tls webhook-server-tls \ + --cert "certs/tls.crt" \ + --key "certs/tls.key" -n "${namespace}" + +ENCODED_CA=$(base64 < certs/tls.crt | tr -d '\n') + +if [ -f "../values.yaml" ]; then + echo -e "\nUpdating values.yaml ..." + sed -i '' 's@${CA_BUNDLE}@'"${ENCODED_CA}"'@g' ../values.yaml +else + echo -e "\nThe CA bundle to use in your values file is: \n${ENCODED_CA}" +fi \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/NOTES.txt b/charts/kubecost/cost-analyzer/2.3.5/templates/NOTES.txt new file mode 100644 index 0000000000..21bd8b1cd9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/NOTES.txt @@ -0,0 +1,27 @@ +-------------------------------------------------- +{{- include "kubecostV2-preconditions" . -}} +{{- include "cloudIntegrationSourceCheck" . -}} +{{- include "eksCheck" . -}} +{{- include "cloudIntegrationSecretCheck" . -}} +{{- include "gcpCloudIntegrationCheck" . -}} +{{- include "azureCloudIntegrationCheck" . -}} +{{- include "federatedStorageConfigSecretCheck" . -}} +{{- include "prometheusRetentionCheck" . -}} +{{- include "clusterIDCheck" . -}} + +{{- $servicePort := .Values.service.port | default 9090 }} +Kubecost {{ .Chart.Version }} has been successfully installed. + +Kubecost 2.x is a major upgrade from previous versions and includes major new features including a brand new API Backend. Please review the following documentation to ensure a smooth transition: https://docs.kubecost.com/install-and-configure/install/kubecostv2 + +When pods are Ready, you can enable port-forwarding with the following command: + + kubectl port-forward --namespace {{ .Release.Namespace }} deployment/{{ template "cost-analyzer.fullname" . }} {{ $servicePort }} + +Then, navigate to http://localhost:{{ $servicePort }} in a web browser. + +Please allow 25 minutes for Kubecost to gather metrics. A progress indicator will appear at the top of the UI. + +Having installation issues? View our Troubleshooting Guide at http://docs.kubecost.com/troubleshoot-install + +{{- include "kubecostV2-3-notices" . -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/_helpers.tpl b/charts/kubecost/cost-analyzer/2.3.5/templates/_helpers.tpl new file mode 100644 index 0000000000..7eda99f3e4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/_helpers.tpl @@ -0,0 +1,1465 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Set important variables before starting main templates +*/}} +{{- define "aggregator.deployMethod" -}} + {{- if (.Values.federatedETL).primaryCluster }} + {{- printf "statefulset" }} + {{- else if or ((.Values.federatedETL).agentOnly) (.Values.agent) (.Values.cloudAgent) }} + {{- printf "disabled" }} + {{- else if (not .Values.kubecostAggregator) }} + {{- printf "singlepod" }} + {{- else if .Values.kubecostAggregator.enabled }} + {{- printf "statefulset" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "singlepod" }} + {{- printf "singlepod" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "statefulset" }} + {{- printf "statefulset" }} + {{- else if eq .Values.kubecostAggregator.deployMethod "disabled" }} + {{- printf "disabled" }} + {{- else }} + {{- fail "Unknown kubecostAggregator.deployMethod value" }} + {{- end }} +{{- end }} + +{{- define "frontend.deployMethod" -}} + {{- if eq .Values.kubecostFrontend.deployMethod "haMode" -}} + {{- printf "haMode" -}} + {{- else -}} + {{- printf "singlepod" -}} + {{- end -}} +{{- end -}} + +{{/* +Kubecost 2.3 notices +*/}} +{{- define "kubecostV2-3-notices" -}} + {{- if (.Values.kubecostAggregator).env -}} + {{- printf "\n\n\nNotice: Issue in values detected.\nKubecost 2.3 has updated the aggregator's environment variables. Please update your Helm values to use the new key pairs.\nFor more information, see: https://docs.kubecost.com/install-and-configure/install/multi-cluster/federated-etl/aggregator#aggregator-optimizations\nIn Kubecost 2.3, kubecostAggregator.env is no longer used in favor of the new key pairs. This was done to prevent unexpected behavior and to simplify the aggregator's configuration." -}} + {{- end -}} +{{- end -}} + +{{/* +Kubecost 2.0 preconditions +*/}} +{{- define "kubecostV2-preconditions" -}} + {{/* Iterate through all StatefulSets in the namespace and check if any of them have a label indicating they are from + a pre-2.0 Helm Chart (e.g. "helm.sh/chart: cost-analyzer-1.108.1"). If so, return an error message with details and + documentation for how to properly upgrade to Kubecost 2.0 */}} + {{- $sts := (lookup "apps/v1" "StatefulSet" .Release.Namespace "") -}} + {{- if not (empty $sts.items) -}} + {{- range $index, $sts := $sts.items -}} + {{- if contains "aggregator" $sts.metadata.name -}} + {{- if $sts.metadata.labels -}} + {{- $stsLabels := $sts.metadata.labels -}} {{/* helm.sh/chart: cost-analyzer-1.108.1 */}} + {{- if hasKey $stsLabels "helm.sh/chart" -}} + {{- $chartLabel := index $stsLabels "helm.sh/chart" -}} {{/* cost-analyzer-1.108.1 */}} + {{- $chartNameAndVersion := split "-" $chartLabel -}} {{/* _0:cost _1:analyzer _2:1.108.1 */}} + {{- if gt (len $chartNameAndVersion) 2 -}} + {{- $chartVersion := $chartNameAndVersion._2 -}} {{/* 1.108.1 */}} + {{- if semverCompare ">=1.0.0-0 <2.0.0-0" $chartVersion -}} + {{- fail "\n\nAn existing Aggregator StatefulSet was found in your namespace.\nBefore upgrading to Kubecost 2.x, please `kubectl delete` this Statefulset.\nRefer to the following documentation for more information: https://docs.kubecost.com/install-and-configure/install/kubecostv2" -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{/*https://github.com/helm/helm/issues/8026#issuecomment-881216078*/}} + {{- if ((.Values.thanos).store).enabled -}} + {{- fail "\n\nYou are attempting to upgrade to Kubecost 2.x.\nKubecost no longer includes Thanos by default. \nPlease see https://docs.kubecost.com/install-and-configure/install/kubecostv2 for more information.\nIf you have any questions or concerns, please reach out to us at product@kubecost.com" -}} + {{- end -}} + + {{- if or ((.Values.saml).rbac).enabled ((.Values.oidc).rbac).enabled -}} + {{- if (not (.Values.upgrade).toV2) -}} + {{- fail "\n\nSSO with RBAC is enabled.\nNote that Kubecost 2.x has significant architectural changes that may impact RBAC.\nThis should be tested before giving end-users access to the UI.\nKubecost has tested various configurations and believe that 2.x will be 100% compatible with existing configurations.\nRefer to the following documentation for more information: https://docs.kubecost.com/install-and-configure/install/kubecostv2\n\nWhen ready to upgrade, add `--set upgrade.toV2=true`." -}} + {{- end -}} + {{- end -}} + + {{- if not .Values.kubecostModel.etlFileStoreEnabled -}} + {{- fail "\n\nKubecost 2.0 does not support running fully in-memory. Some file system must be available to store cost data." -}} + {{- end -}} + + + {{- if .Values.kubecostModel.openSourceOnly -}} + {{- fail "In Kubecost 2.0, kubecostModel.openSourceOnly is not supported" -}} + {{- end -}} + + {{/* Aggregator config reconciliation and common config */}} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" -}} + {{- if .Values.kubecostAggregator -}} + {{- if (not .Values.kubecostAggregator.aggregatorDbStorage) -}} + {{- fail "In Enterprise configuration, Aggregator DB storage is required" -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if (.Values.podSecurityPolicy).enabled }} + {{- fail "Kubecost no longer includes PodSecurityPolicy by default. Please take steps to preserve your existing PSPs before attempting the installation/upgrade again with the podSecurityPolicy values removed." }} + {{- end }} + + {{- if ((.Values.kubecostDeployment).leaderFollower).enabled -}} + {{- fail "\nIn Kubecost 2.0, kubecostDeployment does not support running as leaderFollower. Please reach out to support to discuss upgrade paths." -}} + {{- end -}} + + {{- if ((.Values.kubecostDeployment).statefulSet).enabled -}} + {{- fail "\nIn Kubecost 2.0, kubecostDeployment does not support running as a statefulSet. Please reach out to support to discuss upgrade paths." -}} + {{- end -}} + {{- if and (eq (include "aggregator.deployMethod" .) "statefulset") (.Values.federatedETL).agentOnly }} + {{- fail "\nKubecost does not support running federatedETL.agentOnly with the aggregator statefulset" }} + {{- end }} +{{- end -}} + +{{- define "cloudIntegrationFromProductConfigs" }} + { + {{- if ((.Values.kubecostProductConfigs).athenaBucketName) }} + "aws": [ + { + "athenaBucketName": "{{ .Values.kubecostProductConfigs.athenaBucketName }}", + "athenaRegion": "{{ .Values.kubecostProductConfigs.athenaRegion }}", + "athenaDatabase": "{{ .Values.kubecostProductConfigs.athenaDatabase }}", + "athenaTable": "{{ .Values.kubecostProductConfigs.athenaTable }}", + "projectID": "{{ .Values.kubecostProductConfigs.athenaProjectID }}" + {{ if (.Values.kubecostProductConfigs).athenaWorkgroup }} + , "athenaWorkgroup": "{{ .Values.kubecostProductConfigs.athenaWorkgroup }}" + {{ else }} + , "athenaWorkgroup": "primary" + {{ end }} + {{ if (.Values.kubecostProductConfigs).masterPayerARN }} + , "masterPayerARN": "{{ .Values.kubecostProductConfigs.masterPayerARN }}" + {{ end }} + {{- if and ((.Values.kubecostProductConfigs).awsServiceKeyName) ((.Values.kubecostProductConfigs).awsServiceKeyPassword) }}, + "serviceKeyName": "{{ .Values.kubecostProductConfigs.awsServiceKeyName }}", + "serviceKeySecret": "{{ .Values.kubecostProductConfigs.awsServiceKeyPassword }}" + {{- end }} + } + ] + {{- end }} + } +{{- end }} + +{{/* +Cloud integration source contents check. Either the Secret must be specified or the JSON, not both. +Additionally, for upgrade protection, certain individual values populated under the kubecostProductConfigs map, if found, +will result in failure. Users are asked to select one of the two presently-available sources for cloud integration information. +*/}} +{{- define "cloudIntegrationSourceCheck" -}} + {{- if and (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON -}} + {{- fail "\nkubecostProductConfigs.cloudIntegrationSecret and kubecostProductConfigs.cloudIntegrationJSON are mutually exclusive. Please specify only one." -}} + {{- end -}} + {{- if and (.Values.kubecostProductConfigs).cloudIntegrationSecret ((.Values.kubecostProductConfigs).athenaBucketName) }} + {{- fail "\nkubecostProductConfigs.cloudIntegrationSecret and kubecostProductConfigs.athena* values are mutually exclusive. Please specifiy only one." -}} + {{- end -}} +{{- if and (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + {{- fail "\nkubecostProductConfigs.cloudIntegrationJSON and kubecostProductConfigs.athena* values are mutually exclusive. Please specifiy only one." -}} + {{- end -}} +{{- end -}} + + +{{/* +Print a warning if PV is enabled AND EKS is detected AND the EBS-CSI driver is not installed +*/}} +{{- define "eksCheck" }} +{{- $isEKS := (regexMatch ".*eks.*" (.Capabilities.KubeVersion | quote) )}} +{{- $isGT22 := (semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion) }} +{{- $PVNotExists := (empty (lookup "v1" "PersistentVolume" "" "")) }} +{{- $EBSCSINotExists := (empty (lookup "apps/v1" "Deployment" "kube-system" "ebs-csi-controller")) }} +{{- if (and $isEKS $isGT22 .Values.persistentVolume.enabled $EBSCSINotExists) -}} + +ERROR: MISSING EBS-CSI DRIVER WHICH IS REQUIRED ON EKS v1.23+ TO MANAGE PERSISTENT VOLUMES. LEARN MORE HERE: https://docs.kubecost.com/install-and-configure/install/provider-installations/aws-eks-cost-monitoring#prerequisites + +{{- end -}} +{{- end -}} + +{{/* +Verify a cluster_id is set in the Prometheus global config +*/}} +{{- define "clusterIDCheck" -}} + {{- if (.Values.kubecostModel).federatedStorageConfigSecret }} + {{- if not .Values.prometheus.server.clusterIDConfigmap }} + {{- if eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one" }} + {{- fail "\n\nWhen using multi-cluster Kubecost, you must specify a unique `.Values.prometheus.server.global.external_labels.cluster_id` for each cluster.\nNote this must be set even if you are using your own Prometheus or another identifier.\n" -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{/* +Verify the cloud integration secret exists with the expected key when cloud integration is enabled. +Skip the check if CI/CD is enabled and skipSanityChecks is set. Argo CD, for example, does not +support templating a chart which uses the lookup function. +*/}} +{{- define "cloudIntegrationSecretCheck" -}} +{{- if (.Values.kubecostProductConfigs).cloudIntegrationSecret }} +{{- if not (and .Values.global.platforms.cicd.enabled .Values.global.platforms.cicd.skipSanityChecks) }} +{{- if .Capabilities.APIVersions.Has "v1/Secret" }} + {{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.kubecostProductConfigs.cloudIntegrationSecret }} + {{- if or (not $secret) (not (index $secret.data "cloud-integration.json")) }} + {{- fail (printf "The cloud integration secret '%s' does not exist or does not contain the expected key 'cloud-integration.json'\nIf you are using `--dry-run`, please add `--dry-run=server`. This requires Helm 3.13+." .Values.kubecostProductConfigs.cloudIntegrationSecret) }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Verify the federated storage config secret exists with the expected key when cloud integration is enabled. +Skip the check if CI/CD is enabled and skipSanityChecks is set. Argo CD, for example, does not +support templating a chart which uses the lookup function. +*/}} +{{- define "federatedStorageConfigSecretCheck" -}} +{{- if (.Values.kubecostModel).federatedStorageConfigSecret }} +{{- if not (and .Values.global.platforms.cicd.enabled .Values.global.platforms.cicd.skipSanityChecks) }} +{{- if .Capabilities.APIVersions.Has "v1/Secret" }} + {{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.kubecostModel.federatedStorageConfigSecret }} + {{- if or (not $secret) (not (index $secret.data "federated-store.yaml")) }} + {{- fail (printf "The federated storage config secret '%s' does not exist or does not contain the expected key 'federated-store.yaml'" .Values.kubecostModel.federatedStorageConfigSecret) }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* + Ensure that the Prometheus retention is not set too low +*/}} +{{- define "prometheusRetentionCheck" }} +{{- if ((.Values.prometheus).server).enabled }} + + {{- $retention := .Values.prometheus.server.retention }} + {{- $etlHourlyDurationHours := (int .Values.kubecostModel.etlHourlyStoreDurationHours) }} + + {{- if (hasSuffix "d" $retention) }} + {{- $retentionDays := (int (trimSuffix "d" $retention)) }} + {{- if lt $retentionDays 3 }} + {{- fail (printf "With a daily resolution, Prometheus retention must be set >= 3 days. Provided retention is %s" $retention) }} + {{- else if le (mul $retentionDays 24) $etlHourlyDurationHours }} + {{- fail (printf "Prometheus retention (%s) must be greater than .Values.kubecostModel.etlHourlyStoreDurationHours (%d)" $retention $etlHourlyDurationHours) }} + {{- end }} + + {{- else if (hasSuffix "h" $retention) }} + {{- $retentionHours := (int (trimSuffix "h" $retention)) }} + {{- if lt $retentionHours 50 }} + {{- fail (printf "With an hourly resolution, Prometheus retention must be set >= 50 hours. Provided retention is %s" $retention) }} + {{- else if le $retentionHours $etlHourlyDurationHours }} + {{- fail (printf "Prometheus retention (%s) must be greater than .Values.kubecostModel.etlHourlyStoreDurationHours (%d)" $retention $etlHourlyDurationHours) }} + {{- end }} + + {{- else }} + {{- fail "prometheus.server.retention must be set in days (e.g. 5d) or hours (e.g. 97h)"}} + + {{- end }} +{{- end }} +{{- end }} + +{{/* +Expand the name of the chart. +*/}} +{{- define "cost-analyzer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "aggregator.name" -}} +{{- default "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "cloudCost.name" -}} +{{- default "cloud-cost" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "etlUtils.name" -}} +{{- default "etl-utils" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "forecasting.name" -}} +{{- default "forecasting" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "frontend.name" -}} +{{- default "frontend" | 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 "cost-analyzer.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{- define "diagnostics.fullname" -}} +{{- if .Values.diagnosticsFullnameOverride -}} +{{- .Values.diagnosticsFullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name "diagnostics" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "aggregator.fullname" -}} +{{- printf "%s-%s" .Release.Name "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "cloudCost.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "cloudCost.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "etlUtils.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "etlUtils.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "forecasting.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "forecasting.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "frontend.fullname" -}} +{{- printf "%s-%s" .Release.Name (include "frontend.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the fully qualified name for Prometheus server service. +*/}} +{{- define "cost-analyzer.prometheus.server.name" -}} +{{- if .Values.prometheus -}} +{{- if .Values.prometheus.server -}} +{{- if .Values.prometheus.server.fullnameOverride -}} +{{- .Values.prometheus.server.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-server" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create the fully qualified name for Prometheus alertmanager service. +*/}} +{{- define "cost-analyzer.prometheus.alertmanager.name" -}} +{{- if .Values.prometheus -}} +{{- if .Values.prometheus.alertmanager -}} +{{- if .Values.prometheus.alertmanager.fullnameOverride -}} +{{- .Values.prometheus.alertmanager.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else -}} +{{- printf "%s-prometheus-alertmanager" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "cost-analyzer.serviceName" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{- define "frontend.serviceName" -}} +{{ include "frontend.fullname" . }} +{{- end -}} + +{{- define "diagnostics.serviceName" -}} +{{- printf "%s-%s" .Release.Name "diagnostics" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "aggregator.serviceName" -}} +{{- printf "%s-%s" .Release.Name "aggregator" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- define "cloudCost.serviceName" -}} +{{ include "cloudCost.fullname" . }} +{{- end -}} +{{- define "etlUtils.serviceName" -}} +{{ include "etlUtils.fullname" . }} +{{- end -}} +{{- define "forecasting.serviceName" -}} +{{ include "forecasting.fullname" . }} +{{- end -}} + +{{/* +Create the name of the service account +*/}} +{{- define "cost-analyzer.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "cost-analyzer.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} +{{- define "aggregator.serviceAccountName" -}} +{{- if .Values.kubecostAggregator.serviceAccountName -}} + {{ .Values.kubecostAggregator.serviceAccountName }} +{{- else -}} + {{ template "cost-analyzer.serviceAccountName" . }} +{{- end -}} +{{- end -}} +{{- define "cloudCost.serviceAccountName" -}} +{{- if .Values.kubecostAggregator.cloudCost.serviceAccountName -}} + {{ .Values.kubecostAggregator.cloudCost.serviceAccountName }} +{{- else -}} + {{ template "cost-analyzer.serviceAccountName" . }} +{{- end -}} +{{- end -}} +{{/* +Network Costs name used to tie autodiscovery of metrics to daemon set pods +*/}} +{{- define "cost-analyzer.networkCostsName" -}} +{{- printf "%s-%s" .Release.Name "network-costs" -}} +{{- end -}} + +{{- define "kubecost.clusterControllerName" -}} +{{- printf "%s-%s" .Release.Name "cluster-controller" -}} +{{- end -}} + +{{- define "kubecost.kubeMetricsName" -}} +{{- if .Values.agent }} +{{- printf "%s-%s" .Release.Name "agent" -}} +{{- else if .Values.cloudAgent }} +{{- printf "%s-%s" .Release.Name "cloud-agent" -}} +{{- else }} +{{- printf "%s-%s" .Release.Name "metrics" -}} +{{- end }} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cost-analyzer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the chart labels. +*/}} +{{- define "cost-analyzer.chartLabels" -}} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} +{{- define "kubecost.chartLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} +{{- define "kubecost.aggregator.chartLabels" -}} +app.kubernetes.io/name: {{ include "aggregator.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + + +{{/* +Create the common labels. +*/}} +{{- define "cost-analyzer.commonLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app: cost-analyzer +{{- end -}} + +{{- define "aggregator.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +app: aggregator +{{- end -}} + +{{- define "diagnostics.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +app: diagnostics +{{- end -}} + +{{- define "cloudCost.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "cloudCost.selectorLabels" . }} +{{- end -}} + +{{- define "etlUtils.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "etlUtils.selectorLabels" . }} +{{- end -}} +{{- define "forecasting.commonLabels" -}} +{{ include "cost-analyzer.chartLabels" . }} +{{ include "forecasting.selectorLabels" . }} +{{- end -}} + +{{/* +Create the networkcosts common labels. Note that because this is a daemonset, we don't want app.kubernetes.io/instance: to take the release name, which allows the scrape config to be static. +*/}} +{{- define "networkcosts.commonLabels" -}} +app.kubernetes.io/instance: kubecost +app.kubernetes.io/name: network-costs +helm.sh/chart: {{ include "cost-analyzer.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end -}} +{{- define "networkcosts.selectorLabels" -}} +app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end }} +{{- define "diagnostics.selectorLabels" -}} +app.kubernetes.io/name: diagnostics +app.kubernetes.io/instance: {{ .Release.Name }} +app: diagnostics +{{- end }} + +{{/* +Create the selector labels. +*/}} +{{- define "cost-analyzer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cost-analyzer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: cost-analyzer +{{- end -}} + +{{/* +Create the selector labels for haMode frontend. +*/}} +{{- define "frontend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "frontend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: cost-analyzer +{{- end -}} + +{{- define "aggregator.selectorLabels" -}} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} +app.kubernetes.io/name: {{ include "aggregator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: aggregator +{{- else if eq (include "aggregator.deployMethod" .) "singlepod" }} +{{- include "cost-analyzer.selectorLabels" . }} +{{- else }} +{{ fail "Failed to set aggregator.selectorLabels" }} +{{- end }} +{{- end }} + +{{- define "cloudCost.selectorLabels" -}} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} +app.kubernetes.io/name: {{ include "cloudCost.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "cloudCost.name" . }} +{{- else }} +{{- include "cost-analyzer.selectorLabels" . }} +{{- end }} +{{- end }} + +{{- define "forecasting.selectorLabels" -}} +app.kubernetes.io/name: {{ include "forecasting.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "forecasting.name" . }} +{{- end -}} +{{- define "etlUtils.selectorLabels" -}} +app.kubernetes.io/name: {{ include "etlUtils.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app: {{ include "etlUtils.name" . }} +{{- end -}} + +{{/* +Recursive filter which accepts a map containing an input map (.v) and an output map (.r). The template +will traverse all values inside .v recursively writing non-map values to the output .r. If a nested map +is discovered, we look for an 'enabled' key. If it doesn't exist, we continue traversing the +map. If it does exist, we omit the inner map traversal iff enabled is false. This filter writes the +enabled only version to the output .r +*/}} +{{- define "cost-analyzer.filter" -}} +{{- $v := .v }} +{{- $r := .r }} +{{- range $key, $value := .v }} + {{- $tp := kindOf $value -}} + {{- if eq $tp "map" -}} + {{- $isEnabled := true -}} + {{- if (hasKey $value "enabled") -}} + {{- $isEnabled = $value.enabled -}} + {{- end -}} + {{- if $isEnabled -}} + {{- $rr := "{}" | fromYaml }} + {{- template "cost-analyzer.filter" (dict "v" $value "r" $rr) }} + {{- $_ := set $r $key $rr -}} + {{- end -}} + {{- else -}} + {{- $_ := set $r $key $value -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +This template accepts a map and returns a base64 encoded json version of the map where all disabled +leaf nodes are omitted. + +The implied use case is {{ template "cost-analyzer.filterEnabled" .Values }} +*/}} +{{- define "cost-analyzer.filterEnabled" -}} +{{- $result := "{}" | fromYaml }} +{{- template "cost-analyzer.filter" (dict "v" . "r" $result) }} +{{- $result | toJson | b64enc }} +{{- end -}} + +{{/* +============================================================== +Begin Prometheus templates +============================================================== +*/}} +{{/* +Expand the name of the chart. +*/}} +{{- define "prometheus.name" -}} +{{- "prometheus" -}} +{{- end -}} + +{{/* +Define common selector labels for all Prometheus components +*/}} +{{- define "prometheus.common.matchLabels" -}} +app: {{ template "prometheus.name" . }} +release: {{ .Release.Name }} +{{- end -}} + +{{/* +Define common top-level labels for all Prometheus components +*/}} +{{- define "prometheus.common.metaLabels" -}} +heritage: {{ .Release.Service }} +{{- end -}} + +{{/* +Define top-level labels for Alert Manager +*/}} +{{- define "prometheus.alertmanager.labels" -}} +{{ include "prometheus.alertmanager.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Alert Manager +*/}} +{{- define "prometheus.alertmanager.matchLabels" -}} +component: {{ .Values.prometheus.alertmanager.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Node Exporter +*/}} +{{- define "prometheus.nodeExporter.labels" -}} +{{ include "prometheus.nodeExporter.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Node Exporter +*/}} +{{- define "prometheus.nodeExporter.matchLabels" -}} +component: {{ .Values.prometheus.nodeExporter.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Push Gateway +*/}} +{{- define "prometheus.pushgateway.labels" -}} +{{ include "prometheus.pushgateway.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Push Gateway +*/}} +{{- define "prometheus.pushgateway.matchLabels" -}} +component: {{ .Values.prometheus.pushgateway.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- end -}} + +{{/* +Define top-level labels for Server +*/}} +{{- define "prometheus.server.labels" -}} +{{ include "prometheus.server.matchLabels" . }} +{{ include "prometheus.common.metaLabels" . }} +{{- end -}} + +{{/* +Define selector labels for Server +*/}} +{{- define "prometheus.server.matchLabels" -}} +component: {{ .Values.prometheus.server.name | quote }} +{{ include "prometheus.common.matchLabels" . }} +{{- 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). +*/}} +{{- define "prometheus.fullname" -}} +{{- if .Values.prometheus.fullnameOverride -}} +{{- .Values.prometheus.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified alertmanager name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} + +{{- define "prometheus.alertmanager.fullname" -}} +{{- if .Values.prometheus.alertmanager.fullnameOverride -}} +{{- .Values.prometheus.alertmanager.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.alertmanager.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.alertmanager.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + + +{{/* +Create a fully qualified node-exporter name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.nodeExporter.fullname" -}} +{{- if .Values.prometheus.nodeExporter.fullnameOverride -}} +{{- .Values.prometheus.nodeExporter.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.nodeExporter.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.nodeExporter.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified Prometheus server name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.server.fullname" -}} +{{- if .Values.prometheus.server.fullnameOverride -}} +{{- .Values.prometheus.server.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.prometheus.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.server.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.server.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified pushgateway name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "prometheus.pushgateway.fullname" -}} +{{- if .Values.prometheus.pushgateway.fullnameOverride -}} +{{- .Values.prometheus.pushgateway.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "prometheus" .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.prometheus.pushgateway.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.prometheus.pushgateway.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the alertmanager component +*/}} +{{- define "prometheus.serviceAccountName.alertmanager" -}} +{{- if .Values.prometheus.serviceAccounts.alertmanager.create -}} + {{ default (include "prometheus.alertmanager.fullname" .) .Values.prometheus.serviceAccounts.alertmanager.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.alertmanager.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the nodeExporter component +*/}} +{{- define "prometheus.serviceAccountName.nodeExporter" -}} +{{- if .Values.prometheus.serviceAccounts.nodeExporter.create -}} + {{ default (include "prometheus.nodeExporter.fullname" .) .Values.prometheus.serviceAccounts.nodeExporter.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.nodeExporter.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the pushgateway component +*/}} +{{- define "prometheus.serviceAccountName.pushgateway" -}} +{{- if .Values.prometheus.serviceAccounts.pushgateway.create -}} + {{ default (include "prometheus.pushgateway.fullname" .) .Values.prometheus.serviceAccounts.pushgateway.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.pushgateway.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the server component +*/}} +{{- define "prometheus.serviceAccountName.server" -}} +{{- if .Values.prometheus.serviceAccounts.server.create -}} + {{ default (include "prometheus.server.fullname" .) .Values.prometheus.serviceAccounts.server.name }} +{{- else -}} + {{ default "default" .Values.prometheus.serviceAccounts.server.name }} +{{- end -}} +{{- end -}} + +{{/* +============================================================== +Begin Grafana templates +============================================================== +*/}} +{{/* +Expand the name of the chart. +*/}} +{{- define "grafana.name" -}} +{{- "grafana" -}} +{{- 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 "grafana.fullname" -}} +{{- if .Values.grafana.fullnameOverride -}} +{{- .Values.grafana.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "grafana" .Values.grafana.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account +*/}} +{{- define "grafana.serviceAccountName" -}} +{{- if .Values.grafana.serviceAccount.create -}} + {{ default (include "grafana.fullname" .) .Values.grafana.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.grafana.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +============================================================== +Begin Kubecost 2.0 templates +============================================================== +*/}} + +{{- define "aggregator.containerTemplate" }} +- name: aggregator +{{- if .Values.kubecostAggregator.containerSecurityContext }} + securityContext: + {{- toYaml .Values.kubecostAggregator.containerSecurityContext | nindent 4 }} +{{- else if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 4 }} +{{- end }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostAggregator.fullImageName }} + image: {{ .Values.kubecostAggregator.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- if .Values.kubecostAggregator.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9004 + initialDelaySeconds: {{ .Values.kubecostAggregator.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostAggregator.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostAggregator.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostAggregator.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostAggregator.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: ["waterfowl"] + ports: + - name: tcp-api + containerPort: 9004 + protocol: TCP + {{- with.Values.kubecostAggregator.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} + resources: + {{- toYaml .Values.kubecostAggregator.resources | nindent 4 }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl + readOnly: true + {{- else if eq (include "aggregator.deployMethod" .) "statefulset" }} + {{- fail "When in StatefulSet mode, Aggregator requires that kubecostModel.federatedStorageConfigSecret be set." }} + {{- end }} + {{- if and .Values.persistentVolume.dbPVEnabled (eq (include "aggregator.deployMethod" .) "singlepod") }} + - name: persistent-db + mountPath: /var/db + # aggregator should only need read access to ETL data + readOnly: true + {{- end }} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + - name: aggregator-db-storage + mountPath: /var/configs/waterfowl/duckdb + - name: aggregator-staging + # Aggregator uses /var/configs/waterfowl as a "staging" directory for + # things like intermediate-state files pre-ingestion. In order to avoid a + # permission problem similar to + # https://github.com/kubernetes/kubernetes/issues/81676, we create an + # emptyDir at this path. + # + # This hasn't been observed as a problem in cost-analyzer, likely because + # of the init container that gives everything under /var/configs 777. + mountPath: /var/configs/waterfowl + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: productkey-secret + mountPath: /var/configs/productkey + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).smtp).secretname (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: smtp-secret + mountPath: /var/configs/smtp + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + mountPath: /var/configs/secret-volume + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + mountPath: /var/configs/saml-encryption-cert + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + mountPath: /var/configs/saml-decryption-key + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + mountPath: /var/configs/metadata-secret-volume + {{- end }} + - name: saml-auth-secret + mountPath: /var/configs/saml-auth-secret + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + mountPath: /var/configs/saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + mountPath: /var/configs/oidc + {{- if or .Values.oidc.existingCustomSecret.name .Values.oidc.secretName }} + - name: oidc-client-secret + mountPath: /var/configs/oidc-client-secret + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.global.integrations.postgres.enabled }} + - name: postgres-creds + mountPath: /var/configs/integrations/postgres-creds + - name: postgres-queries + mountPath: /var/configs/integrations/postgres-queries + {{- end }} + {{- /* Only adds extraVolumeMounts if aggregator is running as its own pod */}} + {{- if and .Values.kubecostAggregator.extraVolumeMounts (eq (include "aggregator.deployMethod" .) "statefulset") }} + {{- toYaml .Values.kubecostAggregator.extraVolumeMounts | nindent 4 }} + {{- end }} + env: + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).mountPath (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: PRODUCT_KEY_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.productKey.mountPath }} + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).smtp).mountPath (eq (include "aggregator.deployMethod" .) "statefulset") }} + - name: SMTP_CONFIG_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.smtp.mountPath }} + {{- end }} + {{- if .Values.smtpConfigmapName }} + - name: SMTP_CONFIGMAP_NAME + value: {{ .Values.smtpConfigmapName }} + {{- end }} + {{- if (gt (int .Values.kubecostAggregator.numDBCopyPartitions) 0) }} + - name: NUM_DB_COPY_CHUNKS + value: {{ .Values.kubecostAggregator.numDBCopyPartitions | quote }} + {{- end }} + {{- if .Values.kubecostAggregator.jaeger.enabled }} + - name: TRACING_URL + value: "http://localhost:14268/api/traces" + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + {{- if and .Values.persistentVolume.dbPVEnabled (eq (include "aggregator.deployMethod" .) "singlepod") }} + - name: ETL_PATH_PREFIX + value: "/var/db" + {{- end }} + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API key.This GCP api key is expected to be here and is limited to accessing google's billing API.' + {{- if .Values.global.integrations.postgres.enabled }} + - name: AGGREGATOR_ADDRESS + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + value: localhost:9008 + {{- else }} + value: localhost:9004 + {{- end }} + - name: INT_PG_ENABLED + value: "true" + - name: INT_PG_RUN_INTERVAL + value: {{ quote .Values.global.integrations.postgres.runInterval }} + {{- end }} + - name: READ_ONLY + value: {{ (quote .Values.readonly) | default (quote false) }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if ((.Values.kubecostProductConfigs).carbonEstimates) }} + - name: CARBON_ESTIMATES_ENABLED + value: "true" + {{- end }} + - name: CUSTOM_COST_ENABLED + value: {{ .Values.kubecostModel.plugins.enabled | quote }} + {{- if .Values.kubecostAggregator.extraEnv -}} + {{- toYaml .Values.kubecostAggregator.extraEnv | nindent 4 }} + {{- end }} + {{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + # If this isn't set, we pretty much have to be in a read only state, + # initialization will probably fail otherwise. + - name: ETL_BUCKET_CONFIG + {{- if not .Values.kubecostModel.federatedStorageConfigSecret }} + value: /var/configs/etl/object-store.yaml + {{- else }} + value: /var/configs/etl/federated-store.yaml + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated-store.yaml + - name: FEDERATED_PRIMARY_CLUSTER # this ensures the ingester runs assuming federated primary paths in the bucket + value: "true" + - name: FEDERATED_CLUSTER # this ensures the ingester runs assuming federated primary paths in the bucket + value: "true" + {{- end }} + {{- end }} + - name: LOG_LEVEL + value: {{ .Values.kubecostAggregator.logLevel }} + - name: DB_READ_THREADS + value: {{ .Values.kubecostAggregator.dbReadThreads | quote }} + - name: DB_WRITE_THREADS + value: {{ .Values.kubecostAggregator.dbWriteThreads | quote }} + - name: DB_CONCURRENT_INGESTION_COUNT + value: {{ .Values.kubecostAggregator.dbConcurrentIngestionCount | quote }} + {{- if ne .Values.kubecostAggregator.dbMemoryLimit "0GB" }} + - name: DB_MEMORY_LIMIT + value: {{ .Values.kubecostAggregator.dbMemoryLimit | quote }} + {{- end }} + {{- if ne .Values.kubecostAggregator.dbWriteMemoryLimit "0GB" }} + - name: DB_WRITE_MEMORY_LIMIT + value: {{ .Values.kubecostAggregator.dbWriteMemoryLimit | quote }} + {{- end }} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ .Values.kubecostAggregator.etlDailyStoreDurationDays | quote }} + - name: DB_TRIM_MEMORY_ON_CLOSE + value: {{ .Values.kubecostAggregator.dbTrimMemoryOnClose | quote }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + {{- if .Values.global.grafana }} + - name: GRAFANA_ENABLED + value: "{{ template "cost-analyzer.grafanaEnabled" . }}" + {{- end}} + {{- if .Values.oidc.enabled }} + - name: OIDC_ENABLED + value: "true" + - name: OIDC_SKIP_ONLINE_VALIDATION + value: {{ (quote .Values.oidc.skipOnlineTokenValidation) | default (quote false) }} + {{- end}} + {{- if .Values.kubecostAggregator }} + {{- if .Values.kubecostAggregator.collections }} + {{- if (((.Values.kubecostAggregator).collections).cache) }} + - name: COLLECTIONS_MEMORY_CACHE_ENABLED + value: {{ (quote .Values.kubecostAggregator.collections.cache.enabled) | default (quote true) }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + - name: SAML_ENABLED + value: "true" + - name: IDP_URL + value: {{ .Values.saml.idpMetadataURL }} + - name: SP_HOST + value: {{ .Values.saml.appRootURL }} + {{- if .Values.saml.audienceURI }} + - name: AUDIENCE_URI + value: {{ .Values.saml.audienceURI }} + {{- end }} + {{- if .Values.saml.isGLUUProvider }} + - name: GLUU_SAML_PROVIDER + value: {{ (quote .Values.saml.isGLUUProvider) }} + {{- end }} + {{- if .Values.saml.nameIDFormat }} + - name: NAME_ID_FORMAT + value: {{ .Values.saml.nameIDFormat }} + {{- end}} + {{- if .Values.saml.authTimeout }} + - name: AUTH_TOKEN_TIMEOUT + value: {{ (quote .Values.saml.authTimeout) }} + {{- end}} + {{- if .Values.saml.redirectURL }} + - name: LOGOUT_REDIRECT_URL + value: {{ .Values.saml.redirectURL }} + {{- end}} + {{- if .Values.saml.rbac.enabled }} + - name: SAML_RBAC_ENABLED + value: "true" + {{- end }} + {{- if and .Values.saml.encryptionCertSecret .Values.saml.decryptionKeySecret }} + - name: SAML_RESPONSE_ENCRYPTED + value: "true" + {{- end}} + {{- end }} + {{- end }} +{{- end }} + + +{{- define "aggregator.jaeger.sidecarContainerTemplate" }} +- name: embedded-jaeger + env: + - name: SPAN_STORAGE_TYPE + value: badger + - name: BADGER_EPHEMERAL + value: "true" + - name: BADGER_DIRECTORY_VALUE + value: /tmp/badger/data + - name: BADGER_DIRECTORY_KEY + value: /tmp/badger/key + securityContext: + {{- toYaml .Values.kubecostAggregator.jaeger.containerSecurityContext | nindent 4 }} + image: {{ .Values.kubecostAggregator.jaeger.image }}:{{ .Values.kubecostAggregator.jaeger.imageVersion }} +{{- end }} + + +{{- define "aggregator.cloudCost.containerTemplate" }} +- name: cloud-cost + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostAggregator.fullImageName }} + image: {{ .Values.kubecostAggregator.fullImageName }} + {{- else if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.kubecostAggregator.cloudCost.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9005 + initialDelaySeconds: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostAggregator.cloudCost.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostAggregator.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostAggregator.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: ["cloud-cost"] + ports: + - name: tcp-api + containerPort: 9005 + protocol: TCP + resources: + {{- toYaml .Values.kubecostAggregator.cloudCost.resources | nindent 4 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 4 }} + {{- end }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl/federated + readOnly: true + {{- else if .Values.kubecostModel.etlBucketConfigSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- end }} + {{- if or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + mountPath: /var/configs/cloud-integration + {{- end }} + {{- if .Values.kubecostModel.plugins.enabled }} + - mountPath: {{ .Values.kubecostModel.plugins.folder }} + name: plugins-dir + readOnly: false + - name: tmp + mountPath: /tmp + - mountPath: {{ $.Values.kubecostModel.plugins.folder }}/config + name: plugins-config + readOnly: true + {{- end }} + {{- /* Only adds extraVolumeMounts when cloudcosts is running as its own pod */}} + {{- if and .Values.kubecostAggregator.cloudCost.extraVolumeMounts (eq (include "aggregator.deployMethod" .) "statefulset") }} + {{- toYaml .Values.kubecostAggregator.cloudCost.extraVolumeMounts | nindent 4 }} + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + - name: ETL_BUCKET_CONFIG + value: /var/configs/etl/object-store.yaml + {{- end}} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated/federated-store.yaml + - name: FEDERATED_CLUSTER + value: "true" + {{- end}} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ (quote .Values.kubecostModel.etlDailyStoreDurationDays) }} + - name: CLOUD_COST_REFRESH_RATE_HOURS + value: {{ .Values.kubecostAggregator.cloudCost.refreshRateHours | default 6 | quote }} + - name: CLOUD_COST_QUERY_WINDOW_DAYS + value: {{ .Values.kubecostAggregator.cloudCost.queryWindowDays | default 7 | quote }} + - name: CLOUD_COST_RUN_WINDOW_DAYS + value: {{ .Values.kubecostAggregator.cloudCost.runWindowDays | default 3 | quote }} + - name: CUSTOM_COST_ENABLED + value: {{ .Values.kubecostModel.plugins.enabled | quote }} + {{- range $key, $value := .Values.kubecostAggregator.cloudCost.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} +{{- end }} + +{{/* +SSO enabled flag for nginx configmap +*/}} +{{- define "ssoEnabled" -}} + {{- if or (.Values.saml).enabled (.Values.oidc).enabled -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +To use the Kubecost built-in Teams UI RBAC< you must enable SSO and RBAC and not specify any groups. +Groups is only used when using external RBAC. +*/}} +{{- define "rbacTeamsEnabled" -}} + {{- if or (.Values.saml).enabled (.Values.oidc).enabled -}} + {{- if or ((.Values.saml).rbac).enabled ((.Values.oidc).rbac).enabled -}} + {{- if not (or (.Values.saml).groups (.Values.oidc).groups) -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +Backups configured flag for nginx configmap +*/}} +{{- define "dataBackupConfigured" -}} + {{- if or (.Values.kubecostModel).etlBucketConfigSecret (.Values.kubecostModel).federatedStorageConfigSecret -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{/* +costEventsAuditEnabled flag for nginx configmap +*/}} +{{- define "costEventsAuditEnabled" -}} + {{- if or (.Values.costEventsAudit).enabled -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "cost-analyzer.grafanaEnabled" -}} + {{- if and (.Values.global.grafana.enabled) (not .Values.federatedETL.agentOnly) -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "gcpCloudIntegrationJSON" }} +Kubecost 2.x requires a change to the method that cloud-provider billing integrations are configured. +Please use this output to create a cloud-integration.json config. See: + +for more information + + { + "gcp": + { + [ + { + "bigQueryBillingDataDataset": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataDataset }}", + "bigQueryBillingDataProject": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataProject }}", + "bigQueryBillingDataTable": "{{ .Values.kubecostProductConfigs.bigQueryBillingDataTable }}", + "projectID": "{{ .Values.kubecostProductConfigs.projectID }}" + } + ] + } + } +{{- end }} + +{{- define "gcpCloudIntegrationCheck" }} +{{- if ((.Values.kubecostProductConfigs).bigQueryBillingDataDataset) }} +{{- fail (include "gcpCloudIntegrationJSON" .) }} +{{- end }} +{{- end }} + + +{{- define "azureCloudIntegrationJSON" }} + +Kubecost 2.x requires a change to the method that cloud-provider billing integrations are configured. +Please use this output to create a cloud-integration.json config. See: + +for more information + { + "azure": + [ + { + "azureStorageContainer": "{{ .Values.kubecostProductConfigs.azureStorageContainer }}", + "azureSubscriptionID": "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}", + "azureStorageAccount": "{{ .Values.kubecostProductConfigs.azureStorageAccount }}", + "azureStorageAccessKey": "{{ .Values.kubecostProductConfigs.azureStorageKey }}", + "azureContainerPath": "{{ .Values.kubecostProductConfigs.azureContainerPath }}", + "azureCloud": "{{ .Values.kubecostProductConfigs.azureCloud }}" + } + ] + } +{{- end }} + +{{- define "azureCloudIntegrationCheck" }} +{{- if ((.Values.kubecostProductConfigs).azureStorageContainer) }} +{{- fail (include "azureCloudIntegrationJSON" .) }} +{{- end }} +{{- end }} + +{{- define "clusterControllerEnabled" }} +{{- if (.Values.clusterController).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "forecastingEnabled" }} +{{- if (.Values.forecasting).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "pluginsEnabled" }} +{{- if (.Values.kubecostModel.plugins).enabled }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} + +{{- define "carbonEstimatesEnabled" }} +{{- if ((.Values.kubecostProductConfigs).carbonEstimates) }} +{{- printf "true" -}} +{{- else -}} +{{- printf "false" -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-deployment.yaml new file mode 100644 index 0000000000..b1baf2ff2e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-deployment.yaml @@ -0,0 +1,167 @@ +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +{{/* + A cloud integration secret is required for cloud cost to function as a dedicated pod. + UI based configuration is not supported for cloud cost with aggregator. +*/}} +{{- if ((.Values.kubecostAggregator).cloudCost).enabled }} +{{- if not ( or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName)) }} +{{- fail "\n\nA cloud-integration secret is required when using the aggregator statefulset and cloudCost is enabled." }} +{{- end }} +{{- end }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cloudCost.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cloudCost.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{ include "cloudCost.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + {{/* + Force pod restarts on upgrades to ensure the nginx config is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + app.kubernetes.io/name: cloud-cost + app.kubernetes.io/instance: {{ .Release.Name }} + app: cloud-cost + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cloudCost.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.etlBucketConfigSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if (.Values.kubecostProductConfigs).cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaProjectID) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{/* Despite the name, this is not persistent-configs. + The name is for compatibility with single-pod install. + All data stored here is ephemeral, and does not require persistence. */}} + - name: persistent-configs + emptyDir: {} + {{- if .Values.kubecostModel.plugins.enabled }} + {{- if .Values.kubecostModel.plugins.install.enabled}} + - name: install-script + configMap: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + {{- end }} + - name: plugins-dir + emptyDir: {} + {{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.secretName }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.secretName }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.existingCustomSecret.enabled }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.existingCustomSecret.name }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + - name: tmp + emptyDir: {} + {{- end }} + {{- if .Values.kubecostAggregator.cloudCost.extraVolumes }} + {{- toYaml .Values.kubecostAggregator.cloudCost.extraVolumes | nindent 8 }} + {{- end }} + initContainers: + {{- if (and .Values.kubecostModel.plugins.enabled .Values.kubecostModel.plugins.install.enabled )}} + - name: plugin-installer + image: {{ .Values.kubecostModel.plugins.install.fullImageName }} + command: ["sh", "/install/install_plugins.sh"] + {{- with .Values.kubecostModel.plugins.install.securityContext }} + securityContext: {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: install-script + mountPath: /install + - name: plugins-dir + mountPath: {{ .Values.kubecostModel.plugins.folder }} + {{- end }} + containers: + {{- include "aggregator.cloudCost.containerTemplate" . | nindent 8 }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostAggregator.priority }} + {{- if .Values.kubecostAggregator.priority.enabled }} + {{- if .Values.kubecostAggregator.priority.name }} + priorityClassName: {{ .Values.kubecostAggregator.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-aggregator-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.cloudCost.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service-account.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service-account.yaml new file mode 100644 index 0000000000..c8018f77bb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service-account.yaml @@ -0,0 +1,23 @@ +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +{{/* + A cloud integration secret is required for cloud cost to function as a dedicated pod. + UI based configuration is not supported for cloud cost with aggregator. +*/}} + +{{- if or (.Values.kubecostProductConfigs).cloudIntegrationSecret (.Values.kubecostProductConfigs).cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} +{{- if and .Values.serviceAccount.create .Values.kubecostAggregator.cloudCost.serviceAccountName }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "cloudCost.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cloudCost.commonLabels" . | nindent 4 }} +{{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service.yaml new file mode 100644 index 0000000000..bef9bfdc54 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-cloud-cost-service.yaml @@ -0,0 +1,17 @@ +{{- if not (eq (include "aggregator.deployMethod" .) "disabled") -}} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "cloudCost.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "cloudCost.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "cloudCost.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: tcp-api + port: 9005 + targetPort: 9005 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-service.yaml new file mode 100644 index 0000000000..40f6729de9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-service.yaml @@ -0,0 +1,25 @@ +{{- if not (eq (include "aggregator.deployMethod" .) "disabled") -}} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "aggregator.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "aggregator.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "aggregator.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: tcp-api + port: 9004 + targetPort: 9004 + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + - name: apiserver + port: 9008 + targetPort: 9008 + {{- end }} + {{- with .Values.kubecostAggregator.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-servicemonitor.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-servicemonitor.yaml new file mode 100644 index 0000000000..670ae47947 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-servicemonitor.yaml @@ -0,0 +1,31 @@ +{{- if .Values.serviceMonitor.aggregatorMetrics.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "aggregator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "aggregator.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.aggregatorMetrics.additionalLabels }} + {{ toYaml .Values.serviceMonitor.aggregatorMetrics.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-api + interval: {{ .Values.serviceMonitor.aggregatorMetrics.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.aggregatorMetrics.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.aggregatorMetrics.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.aggregatorMetrics.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "aggregator.commonLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-statefulset.yaml new file mode 100644 index 0000000000..7e8f0469c0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aggregator-statefulset.yaml @@ -0,0 +1,207 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +{{- if eq (include "aggregator.deployMethod" .) "statefulset" }} + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "aggregator.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "aggregator.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.kubecostAggregator.replicas }} + serviceName: {{ template "aggregator.serviceName" . }} + selector: + matchLabels: + {{- include "aggregator.selectorLabels" . | nindent 6 }} + volumeClaimTemplates: + - metadata: + name: aggregator-db-storage + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.kubecostAggregator.aggregatorDbStorage.storageClass }} + resources: + requests: + storage: {{ .Values.kubecostAggregator.aggregatorDbStorage.storageRequest }} + - metadata: + # In the StatefulSet config, Aggregator should not share any filesystem + # state with the cost-model to maintain independence and improve + # stability (in the event of bad file-locking state). Still, there is + # a need to "mount" ConfigMap files (using the watcher) to a file system; + # that's what this per-replica Volume is used for. + name: persistent-configs + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.kubecostAggregator.persistentConfigsStorage.storageClass }} + resources: + requests: + storage: {{ .Values.kubecostAggregator.persistentConfigsStorage.storageRequest }} + template: + metadata: + labels: + app.kubernetes.io/name: aggregator + app.kubernetes.io/instance: {{ .Release.Name }} + {{/* Force pod restarts on upgrades to ensure the nginx config is current */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + app: aggregator + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + {{- if .Values.kubecostAggregator.securityContext }} + securityContext: + {{- toYaml .Values.kubecostAggregator.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "aggregator.serviceAccountName" . }} + volumes: + - name: aggregator-staging + emptyDir: + sizeLimit: {{ .Values.kubecostAggregator.stagingEmptyDirSizeLimit }} + {{- $etlBackupBucketSecret := "" }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + {{- $etlBackupBucketSecret = .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if $etlBackupBucketSecret }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ $etlBackupBucketSecret }} + {{- else }} + {{- fail "\n\nKubecost Aggregator Enterprise Config requires .Values.kubecostModel.federatedStorageConfigSecret" }} + {{- end }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.productKey.secretname }} + items: + - key: productkey.json + path: productkey.json + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).secretname }} + - name: smtp-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.smtp.secretname }} + items: + - key: smtp.json + path: smtp.json + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + secret: + secretName: {{ .Values.saml.secretName }} + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + secret: + secretName: {{ .Values.saml.encryptionCertSecret }} + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + secret: + secretName: {{ .Values.saml.decryptionKeySecret }} + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + secret: + secretName: {{ .Values.saml.metadataSecretName }} + {{- end }} + - name: saml-auth-secret + secret: + secretName: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + configMap: + name: {{ template "cost-analyzer.fullname" . }}-saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + configMap: + name: {{ template "cost-analyzer.fullname" . }}-oidc + {{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.secretName }} + {{- end }} + {{- if .Values.oidc.existingCustomSecret.enabled }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.existingCustomSecret.name }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.global.integrations.postgres.enabled }} + - name: postgres-creds + secret: + {{- if not (eq .Values.global.integrations.postgres.databaseSecretName "") }} + secretName: {{ .Values.global.integrations.postgres.databaseSecretName }} + {{- else }} + secretName: kubecost-integrations-postgres + {{- end }} + - name: postgres-queries + configMap: + name: kubecost-integrations-postgres-queries + {{- end }} + {{- if .Values.kubecostAggregator.extraVolumes }} + {{- toYaml .Values.kubecostAggregator.extraVolumes | nindent 8 }} + {{- end }} + containers: + {{- include "aggregator.containerTemplate" . | nindent 8 }} + + {{- if .Values.kubecostAggregator.jaeger.enabled }} + {{ include "aggregator.jaeger.sidecarContainerTemplate" . | nindent 8 }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostAggregator.priority }} + {{- if .Values.kubecostAggregator.priority.enabled }} + {{- if .Values.kubecostAggregator.priority.name }} + priorityClassName: {{ .Values.kubecostAggregator.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-aggregator-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.kubecostAggregator.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostAggregator.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/alibaba-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/alibaba-service-key-secret.yaml new file mode 100644 index 0000000000..bffb7d8fe6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/alibaba-service-key-secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.alibabaServiceKeyName }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "alibaba_access_key_id": "{{ .Values.kubecostProductConfigs.alibabaServiceKeyName }}", + "alibaba_secret_access_key": "{{ .Values.kubecostProductConfigs.alibabaServiceKeyPassword }}" + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/aws-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/aws-service-key-secret.yaml new file mode 100644 index 0000000000..eeecc03f95 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/aws-service-key-secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.awsServiceKeyName }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "aws_access_key_id": "{{ .Values.kubecostProductConfigs.awsServiceKeyName }}", + "aws_secret_access_key": "{{ .Values.kubecostProductConfigs.awsServiceKeyPassword }}" + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-deployment-template.yaml new file mode 100644 index 0000000000..6a1eb5f8e2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-deployment-template.yaml @@ -0,0 +1,49 @@ +{{- if .Values.awsstore }} +{{- if .Values.awsstore.useAwsStore }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }}-awsstore + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: awsstore + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: awsstore + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: awsstore-serviceaccount + {{- if .Values.awsstore.priorityClassName }} + priorityClassName: "{{ .Values.awsstore.priorityClassName }}" + {{- end }} + {{- with .Values.awsstore.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - image: {{ .Values.awsstore.imageNameAndVersion }} + name: awsstore + # Just sleep forever + command: [ "sleep" ] + args: [ "infinity" ] +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-service-account-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-service-account-template.yaml new file mode 100644 index 0000000000..0dadeaacc0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/awsstore-service-account-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.awsstore }} +{{- if .Values.awsstore.createServiceAccount }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: awsstore-serviceaccount + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- with .Values.awsstore.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/azure-service-key-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/azure-service-key-secret.yaml new file mode 100644 index 0000000000..e61b61e86d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/azure-service-key-secret.yaml @@ -0,0 +1,24 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.createServiceKeySecret }} +{{- if .Values.kubecostProductConfigs.azureSubscriptionID }} +apiVersion: v1 +kind: Secret +metadata: + name: cloud-service-key + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + service-key.json: |- + { + "subscriptionId": "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}", + "serviceKey": { + "appId": "{{ .Values.kubecostProductConfigs.azureClientID }}", + "password": "{{ .Values.kubecostProductConfigs.azureClientPassword }}", + "tenant": "{{ .Values.kubecostProductConfigs.azureTenantID }}" + } + } +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/azure-storage-config-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/azure-storage-config-secret.yaml new file mode 100644 index 0000000000..f27cb4e89d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/azure-storage-config-secret.yaml @@ -0,0 +1,26 @@ +{{/*This method of configuration is deprecated*/}} +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.azureStorageCreateSecret }} +{{- if .Values.kubecostProductConfigs.azureStorageAccessKey }} +{{- if .Values.kubecostProductConfigs.azureStorageAccount }} +{{- if .Values.kubecostProductConfigs.azureStorageContainer }} +apiVersion: v1 +kind: Secret +metadata: + name: azure-storage-config + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + azure-storage-config.json: |- + { + "azureStorageAccount": "{{ .Values.kubecostProductConfigs.azureStorageAccount }}", + "azureStorageAccessKey": "{{ .Values.kubecostProductConfigs.azureStorageAccessKey }}", + "azureStorageContainer": "{{ .Values.kubecostProductConfigs.azureStorageContainer }}" + } +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cloud-integration-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cloud-integration-secret.yaml new file mode 100644 index 0000000000..e6023e59b3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cloud-integration-secret.yaml @@ -0,0 +1,16 @@ +{{- if or ((.Values.kubecostProductConfigs).cloudIntegrationJSON) ((.Values.kubecostProductConfigs).athenaBucketName) }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: cloud-integration + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if (.Values.kubecostProductConfigs).cloudIntegrationJSON }} + cloud-integration.json: {{ .Values.kubecostProductConfigs.cloudIntegrationJSON | replace "\n" "" | b64enc }} + {{- else }} + cloud-integration.json: {{ include "cloudIntegrationFromProductConfigs" . |nindent 4| replace "\n" "" | b64enc }} + {{- end }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-account-mapping-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-account-mapping-configmap.yaml new file mode 100644 index 0000000000..3c4902395c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-account-mapping-configmap.yaml @@ -0,0 +1,12 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.cloudAccountMapping }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "account-mapping" + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + account-map.json: '{{ toJson .Values.kubecostProductConfigs.cloudAccountMapping }}' +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-advanced-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-advanced-reports-configmap.yaml new file mode 100644 index 0000000000..8b31cb0db2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-advanced-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.advancedReports }} +{{- if .Values.global.advancedReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "advanced-report-configs" .Values.advancedReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + advanced-reports.json: '{{ toJson .Values.global.advancedReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-alerts-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-alerts-configmap.yaml new file mode 100644 index 0000000000..3a25544113 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-alerts-configmap.yaml @@ -0,0 +1,10 @@ +{{- if .Values.global.notifications.alertConfigs }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "alert-configs" .Values.alertConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + alerts.json: '{{ toJson .Values.global.notifications.alertConfigs }}' +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-asset-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-asset-reports-configmap.yaml new file mode 100644 index 0000000000..387b0afc83 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-asset-reports-configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.assetReports }} +{{- if .Values.global.assetReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "asset-report-configs" .Values.assetReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + asset-reports.json: '{{ toJson .Values.global.assetReports.reports }}' +{{- end -}} +{{- end -}} + diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cloud-cost-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cloud-cost-reports-configmap.yaml new file mode 100644 index 0000000000..97e74156fc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cloud-cost-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.cloudCostReports }} +{{- if .Values.global.cloudCostReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "cloud-cost-report-configs" .Values.cloudCostReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + cloud-cost-reports.json: '{{ toJson .Values.global.cloudCostReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-binding-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-binding-template.yaml new file mode 100644 index 0000000000..91867dd903 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-binding-template.yaml @@ -0,0 +1,36 @@ +{{- if .Values.reporting }} +{{- if .Values.reporting.logCollection }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cost-analyzer.serviceAccountName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +{{- end }} +{{- end }} +{{- if (not .Values.kubecostModel.etlReadOnlyMode) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "cost-analyzer.serviceAccountName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template-readonly.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template-readonly.yaml new file mode 100644 index 0000000000..c84f105e9c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template-readonly.yaml @@ -0,0 +1,26 @@ +{{- if (.Values.kubecostModel.etlReadOnlyMode) }} +{{- if and .Values.reporting .Values.reporting.logCollection -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ template "cost-analyzer.serviceAccountName" . }} +rules: + - apiGroups: + - '' + resources: + - "pods/log" + verbs: + - get + - list + - watch + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template.yaml new file mode 100644 index 0000000000..a76d2fe55f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-cluster-role-template.yaml @@ -0,0 +1,108 @@ +{{- if not .Values.kubecostModel.etlReadOnlyMode -}} +{{- if and .Values.reporting .Values.reporting.logCollection -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: +- apiGroups: + - '' + resources: + - "pods/log" + verbs: + - get + - list + - watch +- apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - update +--- +{{- end }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: + - apiGroups: + - '' + resources: + - configmaps + - nodes + - pods + - events + - services + - resourcequotas + - replicationcontrollers + - limitranges + - persistentvolumeclaims + - persistentvolumes + - namespaces + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - apps + resources: + - statefulsets + - deployments + - daemonsets + - replicasets + verbs: + - get + - list + - watch + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - get + - list + - watch +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-config-map-template.yaml new file mode 100644 index 0000000000..20033422db --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-config-map-template.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.global.prometheus.enabled }} + {{- if .Values.global.zone }} + prometheus-alertmanager-endpoint: http://{{ template "cost-analyzer.prometheus.alertmanager.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.zone }} + {{ else }} + prometheus-alertmanager-endpoint: http://{{ template "cost-analyzer.prometheus.alertmanager.name" . }}.{{ .Release.Namespace }} + {{- end -}} + {{ else }} + prometheus-alertmanager-endpoint: {{ .Values.global.notifications.alertmanager.fqdn }} + {{- end -}} + {{ if .Values.global.gmp.enabled }} + prometheus-server-endpoint: {{ .Values.global.gmp.prometheusServerEndpoint }} + {{- else if .Values.global.amp.enabled }} + prometheus-server-endpoint: {{ .Values.global.amp.prometheusServerEndpoint }} + {{- else if .Values.global.prometheus.enabled }} + {{- if .Values.global.zone }} + prometheus-server-endpoint: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.global.zone }} + {{ else }} + prometheus-server-endpoint: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }} + {{- end -}} + {{ else }} + prometheus-server-endpoint: {{ .Values.global.prometheus.fqdn }} + {{- end -}} + {{- if .Values.kubecostToken }} + kubecost-token: {{ .Values.kubecostToken }} + {{ else }} + kubecost-token: not-applied + {{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-db-pvc-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-db-pvc-template.yaml new file mode 100644 index 0000000000..9b81ee367e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-db-pvc-template.yaml @@ -0,0 +1,35 @@ +{{- if .Values.persistentVolume -}} +{{- if not .Values.persistentVolume.dbExistingClaim -}} +{{- if .Values.persistentVolume.enabled -}} +{{- if .Values.persistentVolume.dbPVEnabled -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }}-db + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.persistentVolume.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.persistentVolume.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistentVolume.dbStorageClass }} + storageClassName: {{ .Values.persistentVolume.dbStorageClass }} + {{ end }} + resources: + requests: + {{- if .Values.persistentVolume }} + storage: {{ .Values.persistentVolume.dbSize }} + {{- else }} + storage: 32.0Gi + {{ end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-deployment-template.yaml new file mode 100644 index 0000000000..7d9d2ee462 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-deployment-template.yaml @@ -0,0 +1,1191 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.annotations }} + annotations: + {{- toYaml .Values.kubecostDeployment.annotations | nindent 4 }} + {{- end }} +spec: +{{- if .Values.kubecostDeployment }} + replicas: {{ .Values.kubecostDeployment.replicas | default 1 }} +{{- end }} + selector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 8}} +{{- if .Values.kubecostDeployment }} +{{- if .Values.kubecostDeployment.deploymentStrategy }} +{{- with .Values.kubecostDeployment.deploymentStrategy }} + strategy: {{ toYaml . | nindent 4 }} +{{- end }} +{{- else }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate +{{- end }} +{{- end }} + template: + metadata: + labels: + {{- include "cost-analyzer.selectorLabels" . | nindent 8 }} + {{/* Force pod restarts on upgrades to ensure the nginx config is current */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.plugins.enabled }} + - name: plugins-dir + emptyDir: {} + {{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.secretName }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.secretName }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.existingCustomSecret.enabled }} + - name: plugins-config + secret: + secretName: {{ .Values.kubecostModel.plugins.existingCustomSecret.name }} + items: + - key: datadog_config.json + path: datadog_config.json + {{- end }} + {{- if .Values.kubecostModel.plugins.install.enabled}} + - name: install-script + configMap: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + {{- end }} + {{- end }} + {{- if .Values.global.gcpstore.enabled }} + - name: ubbagent-config + configMap: + name: ubbagent-config + {{- end }} + {{- if .Values.hosted }} + - name: config-store + secret: + defaultMode: 420 + secretName: kubecost-thanos + {{- end }} + - name: tmp + emptyDir: {} + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: nginx-conf + configMap: + name: nginx-conf + items: + - key: nginx.conf + path: default.conf + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + - name: var-run + emptyDir: { } + - name: cache + emptyDir: { } + {{- end }} + {{- /* + To opt out of ETL backups, set .Values.kubecostModel.etlBucketConfigSecret="" + */}} + {{- $etlBackupBucketSecret := "" }} + {{- if .Values.kubecostModel.etlBucketConfigSecret }} + {{- $etlBackupBucketSecret = .Values.kubecostModel.etlBucketConfigSecret }} + {{- end }} + {{- if $etlBackupBucketSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ $etlBackupBucketSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.productKey.secretname }} + items: + - key: productkey.json + path: productkey.json + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).secretname }} + - name: smtp-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.smtp.secretname }} + items: + - key: smtp.json + path: smtp.json + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.gcpSecretName }} + items: + - key: {{ .Values.kubecostProductConfigs.gcpSecretKeyName | default "compute-viewer-kubecost-key.json" }} + path: service-key.json + {{- end }} + {{- end -}} + {{- if .Values.kubecostProductConfigs.serviceKeySecretName }} + - name: service-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.serviceKeySecretName }} + {{- else if .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + secret: + secretName: cloud-service-key + {{- end }} + {{- if .Values.kubecostProductConfigs.azureStorageSecretName }} + - name: azure-storage-config + secret: + secretName: {{ .Values.kubecostProductConfigs.azureStorageSecretName }} + items: + - key: azure-storage-config.json + path: azure-storage-config.json + {{- else if .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + secret: + secretName: azure-storage-config + {{- end }} + {{- if .Values.kubecostProductConfigs.cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or .Values.kubecostProductConfigs.cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{- if .Values.kubecostProductConfigs.clusters }} + - name: kubecost-clusters + configMap: + name: kubecost-clusters + {{- range .Values.kubecostProductConfigs.clusters }} + {{- if .auth }} + {{- if .auth.secretName }} + - name: {{ .auth.secretName }} + secret: + secretName: {{ .auth.secretName }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + secret: + secretName : {{ .Values.kubecostFrontend.tls.secretName }} + items: + - key: tls.crt + path: kc.crt + - key: tls.key + path: kc.key + {{- end }} + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: webhook-server-tls + secret: + secretName: {{ .Values.kubecostAdmissionController.secretName }} + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + secret: + secretName: {{ .Values.saml.secretName }} + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + secret: + secretName: {{ .Values.saml.encryptionCertSecret }} + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + secret: + secretName: {{ .Values.saml.decryptionKeySecret }} + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + secret: + secretName: {{ .Values.saml.metadataSecretName }} + {{- end }} + - name: saml-auth-secret + secret: + secretName: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + configMap: + name: {{ template "cost-analyzer.fullname" . }}-saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + configMap: + name: {{ template "cost-analyzer.fullname" . }}-oidc + {{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.secretName }} + {{- end }} + {{- if .Values.oidc.existingCustomSecret.enabled }} + - name: oidc-client-secret + secret: + secretName: {{ .Values.oidc.existingCustomSecret.name }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.extraVolumes }} + # Extra volume(s) + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + - name: persistent-configs +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.existingClaim }} + claimName: {{ .Values.persistentVolume.existingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end }} +{{- if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.dbExistingClaim }} + claimName: {{ .Values.persistentVolume.dbExistingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }}-db +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }}-db +{{- end }} +{{- end }} + initContainers: + {{- if and .Values.kubecostModel.plugins.enabled (not (eq (include "aggregator.deployMethod" .) "statefulset")) }} + - name: plugin-installer + image: {{ .Values.kubecostModel.plugins.install.fullImageName }} + command: ["sh", "/install/install_plugins.sh"] + {{- with .Values.kubecostModel.plugins.install.securityContext }} + securityContext: {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: install-script + mountPath: /install + - name: plugins-dir + mountPath: {{ .Values.kubecostModel.plugins.folder }} + {{- end }} + {{- if .Values.supportNFS }} + - name: config-db-perms-fix + {{- if .Values.initChownDataImage }} + image: {{ .Values.initChownDataImage }} + {{- else }} + image: busybox + {{- end }} + {{- with .Values.initChownData.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.persistentVolume.dbPVEnabled }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs && /bin/chmod -R 777 /var/db"] + {{- else }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs"] + {{- end }} + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db + mountPath: /var/db + {{- end }} + securityContext: + runAsUser: 0 +{{ end }} + containers: + {{- if .Values.global.gmp.enabled }} + - name: {{ .Values.global.gmp.gmpProxy.name }} + image: {{ .Values.global.gmp.gmpProxy.image }} + {{- if .Values.global.gmp.gmpProxy.imagePullPolicy }} + imagePullPolicy: {{ .Values.global.gmp.gmpProxy.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + args: + - "--web.listen-address=:{{ .Values.global.gmp.gmpProxy.port }}" + - "--query.project-id={{ .Values.global.gmp.gmpProxy.projectId }}" + {{- if .Values.systemProxy.enabled }} + env: + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + ports: + - name: web + containerPort: {{ .Values.global.gmp.gmpProxy.port | int }} + readinessProbe: + httpGet: + path: /-/ready + port: web + livenessProbe: + httpGet: + path: /-/healthy + port: web + {{- end }} + {{- if .Values.global.amp.enabled }} + - name: sigv4proxy + image: {{ .Values.sigV4Proxy.image }} + {{- if .Values.sigV4Proxy.imagePullPolicy }} + imagePullPolicy: {{ .Values.sigV4Proxy.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + {{- with .Values.sigV4Proxy.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - --name + - {{ .Values.sigV4Proxy.name }} + - --region + - {{ .Values.sigV4Proxy.region }} + - --host + - {{ .Values.sigV4Proxy.host }} + {{- if .Values.sigV4Proxy.role_arn }} + - --role-arn + - {{ .Values.sigV4Proxy.role_arn }} + {{- end }} + - --port + - :{{ .Values.sigV4Proxy.port }} + ports: + - name: aws-sigv4-proxy + containerPort: {{ .Values.sigV4Proxy.port | int }} + env: + - name: AGENT_LOCAL_PORT + value: "{{ .Values.sigV4Proxy.port | int }}" + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if .Values.sigV4Proxy.extraEnv }} + {{- toYaml .Values.sigV4Proxy.extraEnv | nindent 10 }} + {{- end }} + {{- end }} + {{- if .Values.global.gcpstore.enabled }} + - name: ubbagent + image: gcr.io/kubecost1/gcp-mp/ent/cost-model/ubbagent:1.0 + env: + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + - name: AGENT_CONFIG_FILE + value: "/etc/ubbagent/config.yaml" + - name: AGENT_LOCAL_PORT + value: "6080" + - name: AGENT_ENCODED_KEY + valueFrom: + secretKeyRef: + name: {{ default "kubecost-reporting-secret" .Values.reportingSecret }} + key: reporting-key + - name: AGENT_CONSUMER_ID + valueFrom: + secretKeyRef: + name: {{ default "kubecost-reporting-secret" .Values.reportingSecret }} + key: consumer-id + volumeMounts: + - name: ubbagent-config + mountPath: /etc/ubbagent + {{- end }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + - image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + - image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + name: cost-model + {{- if .Values.kubecostModel.extraArgs }} + args: + {{- toYaml .Values.kubecostModel.extraArgs | nindent 12 }} + {{- end }} + securityContext: + {{- if .Values.kubecostModel.securityContext }} + {{- toYaml .Values.kubecostModel.securityContext | nindent 12 -}} + {{- else if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + ports: + - name: tcp-model + containerPort: 9003 + protocol: TCP + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: tcp-frontend + containerPort: 9090 + protocol: TCP + {{- end }} + {{- with .Values.kubecostModel.extraPorts }} + {{- toYaml . | nindent 10 }} + {{- end }} + resources: +{{ toYaml .Values.kubecostModel.resources | indent 12 }} + {{- if .Values.kubecostModel.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostModel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostModel.readinessProbe.periodSeconds}} + failureThreshold: {{ .Values.kubecostModel.readinessProbe.failureThreshold}} + {{- end }} + {{- if .Values.kubecostModel.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostModel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostModel.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostModel.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.hosted }} + - name: config-store + mountPath: /var/secrets + readOnly: true + {{- end }} + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.extraVolumeMounts }} + # Extra volume mount(s) + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if $etlBackupBucketSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- else if .Values.persistentVolume.dbPVEnabled }} + - name: persistent-db + mountPath: /var/db + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + mountPath: /var/configs/etl/federated + readOnly: true + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: {{ .Values.kubecostAdmissionController.secretName }} + mountPath: /certs + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if and ((.Values.kubecostProductConfigs).productKey).enabled ((.Values.kubecostProductConfigs).productKey).secretname }} + - name: productkey-secret + mountPath: /var/configs/productkey + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).secretname }} + - name: smtp-secret + mountPath: /var/configs/smtp + {{- end }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + mountPath: /var/secrets + {{- end }} + {{- if or .Values.kubecostProductConfigs.azureStorageSecretName .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + mountPath: /var/azure-storage-config + {{- end }} + {{- if or .Values.kubecostProductConfigs.serviceKeySecretName .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + mountPath: /var/secrets + {{- end }} + {{- if .Values.kubecostProductConfigs.clusters }} + - name: kubecost-clusters + mountPath: /var/configs/clusters + {{- range .Values.kubecostProductConfigs.clusters }} + {{- if .auth }} + {{- if .auth.secretName }} + - name: {{ .auth.secretName }} + mountPath: /var/secrets/{{ .auth.secretName }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + {{- if .Values.saml.secretName }} + - name: secret-volume + mountPath: /var/configs/secret-volume + {{- end }} + {{- if .Values.saml.encryptionCertSecret }} + - name: saml-encryption-cert + mountPath: /var/configs/saml-encryption-cert + {{- end }} + {{- if .Values.saml.decryptionKeySecret }} + - name: saml-decryption-key + mountPath: /var/configs/saml-decryption-key + {{- end }} + {{- if .Values.saml.metadataSecretName }} + - name: metadata-secret-volume + mountPath: /var/configs/metadata-secret-volume + {{- end }} + - name: saml-auth-secret + mountPath: /var/configs/saml-auth-secret + {{- if .Values.saml.rbac.enabled }} + - name: saml-roles + mountPath: /var/configs/saml + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc }} + {{- if .Values.oidc.enabled }} + - name: oidc-config + mountPath: /var/configs/oidc + {{- if or .Values.oidc.existingCustomSecret.name .Values.oidc.secretName }} + - name: oidc-client-secret + mountPath: /var/configs/oidc-client-secret + {{- end }} + {{- end }} + {{- end }} + env: + {{- if .Values.global.grafana }} + - name: GRAFANA_ENABLED + value: "{{ template "cost-analyzer.grafanaEnabled" . }}" + {{- end}} + {{- if .Values.kubecostModel.extraEnv -}} + {{ toYaml .Values.kubecostModel.extraEnv | nindent 12 }} + {{- end }} + {{- if .Values.reporting }} + {{- if .Values.reporting.valuesReporting }} + - name: HELM_VALUES + value: {{ template "cost-analyzer.filterEnabled" .Values }} + {{- end }} + {{- end }} + {{- if .Values.alertConfigmapName }} + - name: ALERT_CONFIGMAP_NAME + value: {{ .Values.alertConfigmapName }} + {{- end }} + {{- if .Values.productConfigmapName }} + - name: PRODUCT_CONFIGMAP_NAME + value: {{ .Values.productConfigmapName }} + {{- end }} + {{- if .Values.smtpConfigmapName }} + - name: SMTP_CONFIGMAP_NAME + value: {{ .Values.smtpConfigmapName }} + {{- end }} + {{- if .Values.appConfigmapName }} + - name: APP_CONFIGMAP_NAME + value: {{ .Values.appConfigmapName }} + {{- end }} + {{- if .Values.kubecostModel.softMemoryLimit }} + - name: GOMEMLIMIT + value: {{ .Values.kubecostModel.softMemoryLimit }} + {{- end }} + {{- if .Values.assetReportConfigmapName }} + - name: ASSET_REPORT_CONFIGMAP_NAME + value: {{ .Values.assetReportConfigmapName }} + {{- end }} + {{- if .Values.cloudCostReportConfigmapName }} + - name: CLOUD_COST_REPORT_CONFIGMAP_NAME + value: {{ .Values.cloudCostReportConfigmapName }} + {{- end }} + {{- if .Values.savedReportConfigmapName }} + - name: SAVED_REPORT_CONFIGMAP_NAME + value: {{ .Values.savedReportConfigmapName }} + {{- end }} + {{- if .Values.groupFiltersConfigmapName }} + - name: GROUP_FILTERS_CONFIGMAP_NAME + value: {{ .Values.groupFiltersConfigmapName }} + {{- end }} + {{- if .Values.pricingConfigmapName }} + - name: PRICING_CONFIGMAP_NAME + value: {{ .Values.pricingConfigmapName }} + {{- end }} + {{- if .Values.metricsConfigmapName }} + - name: METRICS_CONFIGMAP_NAME + value: {{ .Values.metricsConfigmapName }} + {{- end }} + - name: READ_ONLY + value: {{ (quote .Values.readonly) | default (quote false) }} + - name: PROMETHEUS_SERVER_ENDPOINT + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: prometheus-server-endpoint + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API key.This GCP api key is expected to be here and is limited to accessing google's billing API. + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/configs/key.json + {{- end }} + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + - name: DB_PATH + value: /var/db/ + - name: CLUSTER_PROFILE + {{- if .Values.kubecostProductConfigs }} + value: {{ .Values.kubecostProductConfigs.clusterProfile | default "production" }} + {{- else }} + value: production + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if ((.Values.kubecostProductConfigs).productKey).mountPath }} + - name: PRODUCT_KEY_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.productKey.mountPath }} + {{- end }} + {{- if ((.Values.kubecostProductConfigs).smtp).mountPath }} + - name: SMTP_CONFIG_MOUNT_PATH + value: {{ .Values.kubecostProductConfigs.smtp.mountPath }} + {{- end }} + {{- if .Values.kubecostProductConfigs.ingestPodUID }} + - name: INGEST_POD_UID + value: {{ (quote .Values.kubecostProductConfigs.ingestPodUID) }} + {{- end }} + {{- if .Values.kubecostProductConfigs.regionOverrides }} + - name: REGION_OVERRIDE_LIST + value: {{ (quote .Values.kubecostProductConfigs.regionOverrides) }} + {{- end }} + {{- end }} + {{- if .Values.global.prometheus.queryServiceBasicAuthSecretName}} + - name: DB_BASIC_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: USERNAME + - name: DB_BASIC_AUTH_PW + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: PASSWORD + {{- end }} + {{- if .Values.global.prometheus.queryServiceBearerTokenSecretName }} + - name: DB_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBearerTokenSecretName }} + key: TOKEN + {{- end }} + {{- if .Values.global.prometheus.insecureSkipVerify }} + - name: INSECURE_SKIP_VERIFY + value: {{ (quote .Values.global.prometheus.insecureSkipVerify) }} + {{- end }} + {{- if .Values.pricingCsv }} + {{- if .Values.pricingCsv.enabled }} + - name: USE_CSV_PROVIDER + value: "true" + - name: CSV_PATH + value: {{ .Values.pricingCsv.location.URI }} + - name: CSV_REGION + value: {{ .Values.pricingCsv.location.region }} + {{- if eq .Values.pricingCsv.location.provider "AWS"}} + {{- if .Values.pricingCsv.location.csvAccessCredentials }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ .Values.pricingCsv.location.csvAccessCredentials }} + key: AWS_ACCESS_KEY_ID + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.pricingCsv.location.csvAccessCredentials }} + key: AWS_SECRET_ACCESS_KEY + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_POD_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitPodAnnotations) | default (quote false) }} + - name: EMIT_NAMESPACE_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitNamespaceAnnotations) | default (quote false) }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_KSM_V1_METRICS + value: {{ (quote .Values.kubecostMetrics.emitKsmV1Metrics) | default (quote true) }} + {{- end }} + {{- if .Values.kubecostMetrics }} + - name: EMIT_KSM_V1_METRICS_ONLY # ONLY emit KSM v1 metrics that do not exist in KSM 2 by default + value: {{ (quote .Values.kubecostMetrics.emitKsmV1MetricsOnly) | default (quote false) }} + {{- end }} + {{- if .Values.reporting }} + - name: LOG_COLLECTION_ENABLED + value: {{ (quote .Values.reporting.logCollection) | default (quote true) }} + - name: PRODUCT_ANALYTICS_ENABLED + value: {{ (quote .Values.reporting.productAnalytics) | default (quote true) }} + - name: ERROR_REPORTING_ENABLED + value: {{ (quote .Values.reporting.errorReporting ) | default (quote true) }} + - name: VALUES_REPORTING_ENABLED + value: {{ (quote .Values.reporting.valuesReporting) | default (quote true) }} + {{- if .Values.reporting.errorReporting }} + - name: SENTRY_DSN + value: "https://71964476292e4087af8d5072afe43abd@o394722.ingest.sentry.io/5245431" + {{- end }} + {{- end }} + - name: LEGACY_EXTERNAL_API_DISABLED + value: {{ (quote .Values.kubecostModel.legacyOutOfClusterAPIDisabled) | default (quote false) }} + - name: CACHE_WARMING_ENABLED + value: {{ (quote .Values.kubecostModel.warmCache) | default (quote true) }} + - name: SAVINGS_ENABLED + value: {{ (quote .Values.kubecostModel.warmSavingsCache) | default (quote true) }} + {{- if $etlBackupBucketSecret }} + - name: ETL_BUCKET_CONFIG + value: "/var/configs/etl/object-store.yaml" + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_STORE_CONFIG + value: "/var/configs/etl/federated/federated-store.yaml" + {{- end }} + {{- if or .Values.federatedETL.federatedCluster .Values.kubecostModel.federatedStorageConfigSecret }} + - name: FEDERATED_CLUSTER + {{- if eq .Values.federatedETL.readOnlyPrimary true }} + value: "false" + {{- else }} + value: "true" + {{- end }} + {{- end }} + {{- if .Values.federatedETL.redirectS3Backup }} + - name: FEDERATED_REDIRECT_BACKUP + value: "true" + {{- end }} + {{- if .Values.federatedETL.useMultiClusterDB }} + - name: CURRENT_CLUSTER_ID_FILTER_ENABLED + value: "true" + {{- end }} + {{- if .Values.persistentVolume.dbPVEnabled }} + - name: ETL_PATH_PREFIX + value: "/var/db" + {{- end }} + - name: ETL_RESOLUTION_SECONDS + value: {{ (quote .Values.kubecostModel.etlResolutionSeconds) | default (quote 300) }} + - name: ETL_MAX_PROMETHEUS_QUERY_DURATION_MINUTES + value: {{ (quote .Values.kubecostModel.maxPrometheusQueryDurationMinutes) | default (quote 1440) }} + - name: ETL_DAILY_STORE_DURATION_DAYS + value: {{ (quote .Values.kubecostModel.etlDailyStoreDurationDays) }} + - name: ETL_HOURLY_STORE_DURATION_HOURS + value: {{ (quote .Values.kubecostModel.etlHourlyStoreDurationHours) | default (quote 49) }} + - name: ETL_WEEKLY_STORE_DURATION_WEEKS + value: {{ (quote .Values.kubecostModel.etlWeeklyStoreDurationWeeks) | default (quote 53) }} + - name: ETL_FILE_STORE_ENABLED + value: {{ (quote .Values.kubecostModel.etlFileStoreEnabled) | default (quote true) }} + - name: ETL_ASSET_RECONCILIATION_ENABLED + value: {{ (quote .Values.kubecostModel.etlAssetReconciliationEnabled) | default (quote true) }} + + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.allocation }} + {{- if .Values.kubecostModel.allocation.nodeLabels }} + {{- with .Values.kubecostModel.allocation.nodeLabels }} + - name: ALLOCATION_NODE_LABELS_ENABLED + value: {{ (quote .enabled) | default (quote true) }} + - name: ALLOCATION_NODE_LABELS_INCLUDE_LIST + value: {{ (quote .includeList) }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + - name: CONTAINER_STATS_ENABLED + value: {{ (quote .Values.kubecostModel.containerStatsEnabled) | default (quote false) }} + - name: RECONCILE_NETWORK + value: {{ (quote .Values.kubecostModel.reconcileNetwork) | default (quote true) }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- if .Values.kubecostMetrics }} + {{- if .Values.kubecostMetrics.exporter }} + - name: KUBECOST_METRICS_POD_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.enabled) | default (quote false) }} + {{- end }} + {{- end }} + - name: PV_ENABLED + value: {{ (quote .Values.persistentVolume.enabled) | default (quote true) }} + - name: MAX_QUERY_CONCURRENCY + value: {{ (quote .Values.kubecostModel.maxQueryConcurrency) | default (quote 5) }} + - name: UTC_OFFSET + value: {{ (quote .Values.kubecostModel.utcOffset) | default (quote ) }} + {{- if .Values.networkCosts }} + {{- if .Values.networkCosts.enabled }} + - name: NETWORK_COSTS_PORT + value: {{ quote .Values.networkCosts.port | default (quote 3001) }} + # ADVANCED_NETWORK_STATS is a feature offered by Kubecost that gives you network + # insights of your Kubernetes resources with cloud services. The feature is + # enabled when network cost is enabled and one of the service tagging is enabled + {{- if .Values.networkCosts.config.services }} + {{- $services := .Values.networkCosts.config.services -}} + {{- if or (index $services "google-cloud-services") (index $services "amazon-web-services") (index $services "azure-cloud-services")}} + - name: ADVANCED_NETWORK_STATS + value: "true" + {{- else}} + - name: ADVANCED_NETWORK_STATS + value: "false" + {{- end}} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.oidc.enabled }} + - name: OIDC_ENABLED + value: "true" + - name: OIDC_SKIP_ONLINE_VALIDATION + value: {{ (quote .Values.oidc.skipOnlineTokenValidation) | default (quote false) }} + {{- end}} + {{- if .Values.saml }} + {{- if .Values.saml.enabled }} + - name: SAML_ENABLED + value: "true" + - name: IDP_URL + value: {{ .Values.saml.idpMetadataURL }} + - name: SP_HOST + value: {{ .Values.saml.appRootURL }} + {{- if .Values.saml.audienceURI }} + - name: AUDIENCE_URI + value: {{ .Values.saml.audienceURI }} + {{- end }} + {{- if .Values.saml.isGLUUProvider }} + - name: GLUU_SAML_PROVIDER + value: {{ (quote .Values.saml.isGLUUProvider) }} + {{- end }} + {{- if .Values.saml.nameIDFormat }} + - name: NAME_ID_FORMAT + value: {{ .Values.saml.nameIDFormat }} + {{- end}} + {{- if .Values.saml.authTimeout }} + - name: AUTH_TOKEN_TIMEOUT + value: {{ (quote .Values.saml.authTimeout) }} + {{- end}} + {{- if .Values.saml.redirectURL }} + - name: LOGOUT_REDIRECT_URL + value: {{ .Values.saml.redirectURL }} + {{- end}} + {{- if .Values.saml.rbac.enabled }} + - name: SAML_RBAC_ENABLED + value: "true" + {{- end }} + {{- if and .Values.saml.encryptionCertSecret .Values.saml.decryptionKeySecret }} + - name: SAML_RESPONSE_ENCRYPTED + value: "true" + {{- end}} + {{- end }} + {{- end }} + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if .Values.kubecostModel.promClusterIDLabel }} + - name: PROM_CLUSTER_ID_LABEL + value: {{ .Values.kubecostModel.promClusterIDLabel }} + {{- end }} + {{- if .Values.hosted }} + - name: KUBECOST_CONFIG_BUCKET + value: /var/secrets/object-store.yaml + - name: CLUSTER_INFO_FILE_ENABLED + value: "true" + - name: CLUSTER_CACHE_FILE_ENABLED + value: "true" + {{- end }} + {{- if .Values.reporting.googleAnalyticsTag }} + - name: GOOGLE_ANALYTICS_TAG + value: {{ .Values.reporting.googleAnalyticsTag }} + {{- end }} + {{- if .Values.costEventsAudit }} + - name: COST_EVENTS_AUDIT_ENABLED + value: {{ (quote .Values.costEventsAudit.enabled) | default (quote false) }} + {{- end }} + - name: RELEASE_NAME + value: {{ .Release.Name }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBECOST_TOKEN + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: kubecost-token + - name: WATERFOWL_ENABLED + value: "true" + {{- if not (.Values.diagnostics.enabled) }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- /*A pre-requisite for running MultiClusterDiagnostics in the cost-model container is a configured federated-store secret and cluster_id*/}} + {{- else if or (empty .Values.kubecostModel.federatedStorageConfigSecret) (eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one") }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- else if .Values.diagnostics.deployment.enabled }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + {{- else }} + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "true" + - name: DIAGNOSTICS_KUBECOST_FQDN + value: "localhost" + - name: DIAGNOSTICS_KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: DIAGNOSTICS_PRIMARY + value: {{ quote .Values.diagnostics.primary.enabled }} + - name: DIAGNOSTICS_RETENTION + value: {{ .Values.diagnostics.primary.retention }} + - name: DIAGNOSTICS_PRIMARY_READONLY + value: {{ quote .Values.diagnostics.primary.readonly }} + - name: DIAGNOSTICS_POLLING_INTERVAL + value: {{ .Values.diagnostics.pollingInterval }} + - name: DIAGNOSTICS_KEEP_HISTORY + value: {{ quote .Values.diagnostics.keepDiagnosticHistory }} + - name: DIAGNOSTICS_COLLECT_HELM_VALUES + value: {{ quote .Values.diagnostics.collectHelmValues }} + {{- end }} + {{- if and .Values.kubecostFrontend.enabled (not .Values.federatedETL.agentOnly) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + {{- if .Values.kubecostFrontend }} + {{- if .Values.kubecostFrontend.fullImageName }} + - image: {{ .Values.kubecostFrontend.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostFrontend.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/frontend-nightly:latest + {{- else }} + - image: {{ .Values.kubecostFrontend.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/frontend:prod-{{ $.Chart.AppVersion }} + {{- end }} + env: + - name: GET_HOSTS_FROM + value: dns + {{- if .Values.kubecostFrontend.extraEnv -}} + {{ toYaml .Values.kubecostFrontend.extraEnv | nindent 12 }} + {{- end }} + name: cost-analyzer-frontend + {{- if .Values.kubecostFrontend.securityContext }} + securityContext: + {{- toYaml .Values.kubecostFrontend.securityContext | nindent 12 }} + {{- else }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nginx-conf + mountPath: /etc/nginx/conf.d/ + {{- if .Values.global.containerSecuritycontext }} + - mountPath: /var/cache/nginx + name: cache + - mountPath: /var/run + name: var-run + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + mountPath: /etc/ssl/certs + {{- end }} + {{- end }} + resources: +{{ toYaml .Values.kubecostFrontend.resources | indent 12 }} + {{- if .Values.kubecostFrontend.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostFrontend.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.kubecostFrontend.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostFrontend.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostFrontend.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9003 + initialDelaySeconds: {{ .Values.kubecostFrontend.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + {{ end }} + + {{- if and (eq (include "aggregator.deployMethod" .) "singlepod") (not .Values.federatedETL.agentOnly) }} + {{- include "aggregator.containerTemplate" . | nindent 8 }} + {{- if .Values.kubecostAggregator.jaeger.enabled }} + {{- include "aggregator.jaeger.sidecarContainerTemplate" . | nindent 8 }} + {{- end }} + {{- include "aggregator.cloudCost.containerTemplate" . | nindent 8 }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.priority }} + {{- if .Values.priority.enabled }} + {{- if gt (len .Values.priority.name) 0 }} + priorityClassName: {{ .Values.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-frontend-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-frontend-config-map-template.yaml new file mode 100644 index 0000000000..4cfabac0e8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-frontend-config-map-template.yaml @@ -0,0 +1,1328 @@ +{{- if .Values.kubecostFrontend.enabled }} +{{- if and (not .Values.agent) (not .Values.cloudAgent) (not .Values.federatedETL.agentOnly) }} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +{{- if .Values.saml.enabled }} +{{- if .Values.oidc.enabled }} +{{- fail "SAML and OIDC cannot both be enabled" }} +{{- end }} +{{- end }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-conf + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + nginx.conf: | + gzip_static on; + + # Enable gzip encoding for content of the provided types of 50kb and higher. + gzip on; + gzip_min_length 50000; + gzip_proxied expired no-cache no-store private auth; + gzip_types + application/atom+xml + application/geo+json + application/javascript + application/x-javascript + application/json + application/ld+json + application/manifest+json + application/rdf+xml + application/rss+xml + application/vnd.ms-fontobject + application/wasm + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/eot + font/otf + font/ttf + image/bmp + image/svg+xml + text/cache-manifest + text/calendar + text/css + text/javascript + text/markdown + text/plain + text/xml + text/x-component + text/x-cross-domain-policy; + + upstream api { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ $serviceName }}.{{ .Release.Namespace }}.svc.cluster.local:9001; +{{- else if (.Values.kubecostFrontend.api).fqdn }} + server {{ .Values.kubecostFrontend.api.fqdn }}; +{{- else }} + server {{ $serviceName }}.{{ .Release.Namespace }}:9001; +{{- end }} + } + + upstream model { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ $serviceName }}.{{ .Release.Namespace }}.svc.cluster.local:9003; +{{- else if (.Values.kubecostFrontend.model).fqdn }} + server {{ .Values.kubecostFrontend.model.fqdn }}; +{{- else }} + server {{ $serviceName }}.{{ .Release.Namespace }}:9003; +{{- end }} + } + +{{- if and .Values.clusterController .Values.clusterController.enabled }} + upstream clustercontroller { +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "kubecost.clusterControllerName" . }}-service.{{ .Release.Namespace }}.svc.cluster.local:9731; +{{- else }} +{{- if (.Values.kubecostFrontend.clusterController).fqdn }} + server {{ .Values.kubecostFrontend.clusterController.fqdn }}; +{{- else }} + server {{ template "kubecost.clusterControllerName" . }}-service.{{ .Release.Namespace }}:9731; +{{- end }} +{{- end }} + } +{{- end }} + +{{- if .Values.global.grafana.proxy }} + upstream grafana { +{{- if .Values.global.grafana.enabled }} +{{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-grafana.{{ .Release.Namespace }}.svc.cluster.local; +{{- else }} +{{- if .Values.global.grafana.fqdn }} + server {{ .Values.global.grafana.fqdn }}; +{{- else }} + server {{ .Release.Name }}-grafana.{{ .Release.Namespace }}; +{{- end }} +{{- end }} +{{- else }} + server {{.Values.global.grafana.domainName}}; +{{- end }} + } +{{- end }} + + {{- if .Values.forecasting.enabled }} + upstream forecasting { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-forecasting.{{ .Release.Namespace }}.svc.cluster.local:5000; + {{- else }} + {{- if (.Values.kubecostFrontend.forcasting).fqdn }} + server {{ .Values.kubecostFrontend.forcasting.fqdn }}; + {{- else }} + server {{ .Release.Name }}-forecasting.{{ .Release.Namespace }}:5000; + {{- end }} + {{- end }} + } + {{- end }} + + {{- if and (not .Values.agent) (not .Values.cloudAgent) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + upstream aggregator { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ .Release.Name }}-aggregator.{{ .Release.Namespace }}.svc.cluster.local:9004; + {{- else }} + {{- if (.Values.kubecostFrontend.aggregator).fqdn }} + server {{ .Values.kubecostFrontend.aggregator.fqdn }}; + {{- else }} + server {{ .Release.Name }}-aggregator.{{ .Release.Namespace }}:9004; + {{- end }} + {{- end }} + } + upstream cloudCost { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "cloudCost.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local:9005; + {{- else }} + {{- if (.Values.kubecostFrontend.cloudCost).fqdn }} + server {{ .Values.kubecostFrontend.cloudCost.fqdn }}; + {{- else }} + server {{ template "cloudCost.serviceName" . }}.{{ .Release.Namespace }}:9005; + {{- end }} + {{- end }} + } + {{- end }} + + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled .Values.diagnostics.deployment.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + upstream multi-cluster-diagnostics { + {{- if .Values.kubecostFrontend.useDefaultFqdn }} + server {{ template "diagnostics.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:9007; + {{- else}} + {{- if (.Values.kubecostFrontend.multiClusterDiagnostics).fqdn }} + server {{ .Values.kubecostFrontend.multiClusterDiagnostics.fqdn }}; + {{- else }} + server {{ template "diagnostics.fullname" . }}.{{ .Release.Namespace }}:9007; + {{- end }} + {{- end }} + } + {{- end }} + {{- end }} + + server { + server_name _; + root /var/www; + index index.html; + + add_header Cache-Control "must-revalidate"; + + {{- if .Values.kubecostFrontend.extraServerConfig }} + {{- .Values.kubecostFrontend.extraServerConfig | toString | nindent 8 -}} + {{- else }} + large_client_header_buffers 4 32k; + {{- end }} + + error_page 504 /custom_504.html; + location = /custom_504.html { + internal; + } + +{{- if or .Values.saml.enabled .Values.oidc.enabled }} + add_header Cache-Control "max-age=0"; + location / { + auth_request /auth; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + error_page 401 = /login; + try_files $uri $uri/ /index.html; + } + location /healthz { + add_header 'Content-Type' 'text/plain'; + return 200 "healthy\n"; + } +{{- else }} + add_header Cache-Control "max-age=300"; + location / { + try_files $uri $uri/ /index.html; + } +{{- end }} +{{- if .Values.imageVersion }} + add_header ETag "{{ $.Values.imageVersion }}"; +{{- else }} + add_header ETag "{{ $.Chart.Version }}"; +{{- end }} +{{- if .Values.kubecostFrontend.tls }} +{{- if .Values.kubecostFrontend.tls.enabled }} +{{- if .Values.kubecostFrontend.tls.specifyProtocols }} + ssl_protocols {{ $.Values.kubecostFrontend.tls.protocols }}; +{{- end }} + ssl_certificate /etc/ssl/certs/kc.crt; + ssl_certificate_key /etc/ssl/certs/kc.key; + listen {{ .Values.service.targetPort }} ssl; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }} ssl; +{{- end }} +{{- else }} + listen {{ .Values.service.targetPort }}; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }}; +{{- end }} +{{- end }} +{{- else }} + listen {{ .Values.service.targetPort }}; +{{- if .Values.kubecostFrontend.ipv6.enabled }} + listen [::]:{{ .Values.service.targetPort }}; +{{- end }} +{{- end }} + location /api/ { + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + auth_request /auth; + {{- end }} + proxy_pass http://api/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /model/ { + proxy_connect_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_send_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://model/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + {{- if .Values.kubecostFrontend.extraModelConfigs }} + {{- .Values.kubecostFrontend.extraModelConfigs | toString | nindent 12 -}} + {{- end }} + } + + location ~ ^/(turndown|cluster)/ { + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} + {{- if or .Values.saml .Values.oidc }} + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + auth_request /auth; + {{- else if .Values.saml.rbac.enabled}} + auth_request /authrbac; + {{- end }} + {{- end }} + + rewrite ^/(?:turndown|cluster)/(.*)$ /$1 break; + proxy_pass http://clustercontroller; + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + +{{- else }} + return 404; +{{- end }} +{{- else }} + return 404; +{{- end }} + } +{{- if and (or .Values.saml.enabled .Values.oidc.enabled) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + {{- if .Values.oidc.enabled }} + location /oidc/ { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/oidc/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} + {{- if .Values.saml.enabled }} + location /saml/ { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/saml/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + location /login { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/login; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Original-URI $request_uri; + } + + location /logout { + proxy_connect_timeout 180; + proxy_send_timeout 180; + proxy_read_timeout 180; + proxy_pass http://aggregator/logout; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- end }} +{{- end }} + {{- if .Values.global.grafana.proxy }} + location /grafana/ { + {{- if .Values.saml.enabled }} + auth_request /auth; + {{- end }} + proxy_pass http://grafana/; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + } + {{ end }} + {{- if .Values.oidc.enabled }} + location /auth { + proxy_pass http://aggregator/isAuthenticated; + } + {{- end }} + {{- if .Values.saml.enabled }} + location /auth { + proxy_pass http://aggregator/isAuthenticated; + } + {{- if .Values.saml.rbac.enabled }} + location /authrbac { + proxy_pass http://aggregator/isAdminAuthenticated; + } + {{- end }} + {{- end }} + + +{{- if and (not .Values.agent) (not .Values.cloudAgent) (not (eq (include "aggregator.deployMethod" .) "disabled")) }} + # TODO make aggregator route the default, start special-casing + # cost-model APIs + + # Aggregator proxy + {{- if and (.Values.kubecostDeployment) (.Values.kubecostDeployment.queryServiceReplicas) (gt (.Values.kubecostDeployment.queryServiceReplicas | toString | atoi) 0) }} + {{- fail "The Kubecost Aggregator should not be used at the same time as Query Service Replicas" }} + {{- end }} + + location = /model/allocation { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- if not .Values.kubecostFrontend.trendsDisabled }} + location = /model/allocation/trends { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/trends; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{ end }} + location = /model/allocation/view { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/view; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/summary { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/summary; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/summary/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/allocation/summary/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/allocation/carbon { + proxy_read_timeout 300; + proxy_pass http://aggregator/allocation/carbon; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/assets; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/topline { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/graph { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/totals { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/diff { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/breakdown { + return 501 "Aggregator does not support this endpoint."; + } + location = /model/assets/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/assets/carbon { + proxy_read_timeout 300; + proxy_pass http://aggregator/assets/carbon; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/requestSizingV2 { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/requestSizingV2; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/requestSizingV2/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/requestSizingV2/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/clusterSizingETL { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/clusterSizingETL; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/graph { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/totals { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/totals; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/table { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/table; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/view/trends { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/cloudCost/view/trends; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/autocomplete { + proxy_read_timeout 300; + proxy_pass http://aggregator/cloudCost/autocomplete; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/clusters/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/clusters/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/abandonedWorkloads { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/abandonedWorkloads; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/abandonedWorkloads/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/abandonedWorkloads/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/unclaimedVolumes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/unclaimedVolumes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/localLowDisks { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/localLowDisks; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/persistentVolumeSizing { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/persistentVolumeSizing; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/savings/persistentVolumeSizing/topline { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/savings/persistentVolumeSizing/topline; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/allocation { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/allocation; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/asset { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/asset; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/advanced { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/advanced; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/cloudCost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/cloudCost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/reports/group { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/group; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # this is a special case to handle /reports/group/:group in the Kubecost Aggregator. prior to aggregator, this endpoint + # was handled by /model/, so no special case proxies were required. without this, /model/reports/groups/?foo=bar + # will be directed to /reports/groups?foo=bar (note the missing /model prefix) + location ~ ^/model/reports/group/ { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/reports/group/$is_args$args; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/budget { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/budget; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/budgets { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/budgets; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/total { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/timeseries { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement/cloud { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement/cloud; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/query/complement/kubernetes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/query/complement/kubernetes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location = /model/collections/query/total { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/timeseries { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement/cloud { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement/cloud; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collections/query/complement/kubernetes { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collections/query/complement/kubernetes; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/collection/cache/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/collection/cache/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/networkinsights { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/networkinsights; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/networkinsights/graph { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/networkinsights/graph; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/rbacGroups { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/rbacGroups; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/smtp { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/smtp; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/smtp/test { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/smtp/test; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/teams { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/teams; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/team { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/team; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/users { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/users; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/user { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/user; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/serviceAccounts { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/serviceAccounts; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/serviceAccount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/serviceAccount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/orchestrator { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/orchestrator; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/prediction/speccost { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/prediction/speccost; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/coreCount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/coreCount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/tableWindowCount { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/tableWindowCount; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containersPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containersPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/nodesPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/nodesPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerLabelStats { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerLabelStats; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerAnnotationStats { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerAnnotationStats; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/cloudCostsPerDay { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/cloudCostsPerDay; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerWithoutMatchingNode { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerWithoutMatchingNode; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerDuplicateNoId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerDuplicateNoId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/containerDuplicateWithId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/containerDuplicateWithId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/nodeDuplicateNoId { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/nodeDuplicateNoId; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/ingestionRecords { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/ingestionRecords; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/ingestionSummary { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/ingestionSummary; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/debug/derivationRecords { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/debug/derivationRecords; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/diagnostic/databaseDirectory { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/diagnostic/databaseDirectory; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/getApiConfig { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/getApiConfig; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/setApiConfig { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://aggregator/setApiConfig; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/enablements { + proxy_read_timeout 300; + proxy_pass http://aggregator/enablements; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/total { + proxy_read_timeout 300; + proxy_pass http://aggregator/customCost/total; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/timeseries { + proxy_read_timeout 300; + proxy_pass http://aggregator/customCost/timeseries; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/providerOptimization { + proxy_read_timeout 300; + proxy_pass http://aggregator/providerOptimization; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location = /model/getProductKey { + proxy_read_timeout 300; + proxy_pass http://aggregator/getProductKey; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/setProductKey { + proxy_read_timeout 300; + proxy_pass http://aggregator/setProductKey; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/trialStatus { + proxy_read_timeout 300; + proxy_pass http://aggregator/trialStatus; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/startProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/startProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/resetProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/resetProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/extendProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/extendProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/expireProductTrial { + proxy_read_timeout 300; + proxy_pass http://aggregator/expireProductTrial; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + #Cloud Cost Endpoints + location = /model/cloudCost/status { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/rebuild { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/rebuild; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/repair { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/repair; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/export { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/export; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/enable { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/enable; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloud/config/disable { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloud/config/disable; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/cloudCost/integration/validate { + proxy_read_timeout {{ .Values.kubecostFrontend.timeoutSeconds | default 300 }}; + proxy_pass http://cloudCost/cloudCost/integration/validate; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/status { + proxy_read_timeout 300; + proxy_pass http://cloudCost/customCost/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location = /model/customCost/rebuild { + proxy_read_timeout 300; + proxy_pass http://cloudCost/customCost/rebuild; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +{{- end }} + location = /model/hideOrphanedResources { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if .Values.kubecostFrontend.hideOrphanedResources }} + return 200 '{"hideOrphanedResources": "true"}'; + {{- else }} + return 200 '{"hideOrphanedResources": "false"}'; + {{- end }} + } + location = /model/hideDiagnostics { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if .Values.kubecostFrontend.hideDiagnostics }} + return 200 '{"hideDiagnostics": "true"}'; + {{- else }} + return 200 '{"hideDiagnostics": "false"}'; + {{- end }} + } + + {{- if .Values.kubecostFrontend.trendsDisabled }} + location /model/allocation/trends { + return 204 'endpoint disabled'; + } + {{ end }} + + location /model/multi-cluster-diagnostics-enabled { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + return 200 '{"multiClusterDiagnosticsEnabled": true}'; + {{- end }} + {{- else }} + return 200 '{"multiClusterDiagnosticsEnabled": false}'; + {{- end }} + } + + {{- if and .Values.diagnostics.enabled .Values.diagnostics.primary.enabled .Values.diagnostics.deployment.enabled }} + {{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) }} + + # When the Multi-cluster Diagnostics Service is run within the + # cost-model container, its endpoint is available at the path + # `/model/diagnostics/multicluster`. No additional Nginx path forwarding + # needed. When the Multi-cluster Diagnostics Service is run as a K8s + # Deployment, we should forward that path to the K8s Service. + location /model/diagnostics/multicluster { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://multi-cluster-diagnostics/status; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + # simple alias for support + location /mcd { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://multi-cluster-diagnostics/status?window=7d; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + {{- end }} + {{- end }} + + location /model/aggregatorEnabled { + default_type 'application/json'; + return 200 '{"aggregatorEnabled": "true"}'; + } + + {{- if .Values.forecasting.enabled }} + location /forecasting { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + proxy_read_timeout 300; + proxy_pass http://forecasting/; + proxy_redirect off; + proxy_set_header Connection ""; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + {{- else }} + location /forecasting { + default_type 'application/json'; + return 405 '{"forecastingEnabled": "false"}'; + } + {{- end }} + + location /model/productConfigs { + default_type 'application/json'; + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, OPTIONS' always; + return 200 '\n + { + "ssoConfigured": "{{ template "ssoEnabled" . }}", + "rbacTeamsEnabled": "{{ template "rbacTeamsEnabled" . }}", + "dataBackupConfigured": "{{ template "dataBackupConfigured" . }}", + "costEventsAuditEnabled": "{{ template "costEventsAuditEnabled" . }}", + "frontendDeployMethod": "{{ template "frontend.deployMethod" . }}", + "pluginsEnabled": "{{ template "pluginsEnabled" . }}", + "carbonEstimatesEnabled": "{{ template "carbonEstimatesEnabled" . }}", + "clusterControllerEnabled": "{{ template "clusterControllerEnabled" . }}", + "forecastingEnabled": "{{ template "forecastingEnabled" . }}", + "chartVersion": "2.3.5" + } + '; + } + } + +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ingress-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ingress-template.yaml new file mode 100644 index 0000000000..4ac0693ddf --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ingress-template.yaml @@ -0,0 +1,56 @@ +{{- if .Values.ingress -}} +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "cost-analyzer.fullname" . -}} +{{- $serviceName := "" -}} +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +{{- $serviceName = include "frontend.serviceName" . }} +{{- else }} +{{- $serviceName = include "cost-analyzer.serviceName" . -}} +{{- end }} +{{- $ingressPaths := .Values.ingress.paths -}} +{{- $ingressPathType := .Values.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} +{{- end }} +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + {{- range $ingressPaths }} + - path: {{ . }} + pathType: {{ $ingressPathType }} + backend: + service: + name: {{ $serviceName }} + port: + name: tcp-frontend + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-metrics-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-metrics-config-map-template.yaml new file mode 100644 index 0000000000..136d7fa9a7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-metrics-config-map-template.yaml @@ -0,0 +1,13 @@ +{{- if .Values.kubecostProductConfigs -}} +{{- if .Values.kubecostProductConfigs.metricsConfigs -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "metrics-config" .Values.metricsConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + metrics.json: '{{ toJson .Values.kubecostProductConfigs.metricsConfigs }}' +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-config-map-template.yaml new file mode 100644 index 0000000000..378fca5849 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-config-map-template.yaml @@ -0,0 +1,16 @@ +{{- if .Values.networkCosts -}} +{{- if .Values.networkCosts.enabled -}} +{{- if .Values.networkCosts.config -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: network-costs-config + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + config.yaml: | +{{- toYaml .Values.networkCosts.config | nindent 4 }} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-podmonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-podmonitor-template.yaml new file mode 100644 index 0000000000..d0b5b5dd8f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-podmonitor-template.yaml @@ -0,0 +1,32 @@ +{{- if .Values.networkCosts }} +{{- if .Values.networkCosts.enabled }} +{{- if .Values.networkCosts.podMonitor }} +{{- if .Values.networkCosts.podMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ include "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.podMonitor.additionalLabels }} + {{ toYaml .Values.networkCosts.podMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + podMetricsEndpoints: + - port: http-server + honorLabels: true + interval: 1m + scrapeTimeout: 10s + path: /metrics + scheme: http + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ template "cost-analyzer.networkCostsName" . }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-service-template.yaml new file mode 100644 index 0000000000..0ac70718d4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-service-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.networkCosts }} +{{- if .Values.networkCosts.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} +{{- if (or .Values.networkCosts.service.annotations .Values.networkCosts.prometheusScrape) }} + annotations: +{{- if .Values.networkCosts.service.annotations }} +{{ toYaml .Values.networkCosts.service.annotations | indent 4 }} +{{- end }} +{{- if .Values.networkCosts.prometheusScrape }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ (quote .Values.networkCosts.port) | default (quote 3001) }} +{{- end }} +{{- end }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.service.labels }} + {{ toYaml .Values.networkCosts.service.labels | nindent 4 }} + {{- end }} +spec: + clusterIP: None + ports: + - name: metrics + port: {{ .Values.networkCosts.port | default 3001 }} + protocol: TCP + targetPort: {{ .Values.networkCosts.port | default 3001 }} + selector: + {{- include "networkcosts.selectorLabels" . | nindent 4 }} + type: ClusterIP +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-template.yaml new file mode 100644 index 0000000000..7af7881535 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-costs-template.yaml @@ -0,0 +1,149 @@ +{{- if .Values.networkCosts -}} +{{- if .Values.networkCosts.enabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 4 }} + {{- if .Values.networkCosts.additionalLabels }} + {{- toYaml .Values.networkCosts.additionalLabels | nindent 4 }} + {{- end }} +spec: + {{- if .Values.networkCosts.updateStrategy }} + updateStrategy: + {{- toYaml .Values.networkCosts.updateStrategy | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "networkcosts.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.networkCosts.annotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "networkcosts.commonLabels" . | nindent 8 }} + {{- if .Values.networkCosts.additionalLabels }} + {{- toYaml .Values.networkCosts.additionalLabels | nindent 8 }} + {{- end }} + spec: + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + hostNetwork: true + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + containers: + - name: {{ template "cost-analyzer.networkCostsName" . }} + {{- if eq (typeOf .Values.networkCosts.image) "string" }} + image: {{ .Values.networkCosts.image }} + {{- else }} + image: {{ .Values.networkCosts.image.repository }}:{{ .Values.networkCosts.image.tag }} + {{- end}} + {{- if .Values.networkCosts.extraArgs }} + args: + {{- toYaml .Values.networkCosts.extraArgs | nindent 8 }} + {{- end }} +{{- if .Values.networkCosts.imagePullPolicy }} + imagePullPolicy: {{ .Values.networkCosts.imagePullPolicy }} +{{- else }} + imagePullPolicy: Always +{{- end }} +{{- if .Values.networkCosts.resources }} + resources: {{- toYaml .Values.networkCosts.resources | nindent 10 }} +{{- end }} + env: + {{- if .Values.networkCosts.hostProc }} + - name: HOST_PROC + value: {{ .Values.networkCosts.hostProc.mountPath }} + {{- end }} + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: HOST_PORT + value: {{ (quote .Values.networkCosts.port) | default (quote 3001) }} + - name: TRAFFIC_LOGGING_ENABLED + value: {{ (quote .Values.networkCosts.trafficLogging) | default (quote true) }} + - name: LOG_LEVEL + value: {{ .Values.networkCosts.logLevel | default "info" }} + {{- if .Values.networkCosts.softMemoryLimit }} + - name: GOMEMLIMIT + value: {{ .Values.networkCosts.softMemoryLimit }} + {{- end }} + {{- if .Values.networkCosts.heapMonitor }} + {{- if .Values.networkCosts.heapMonitor.enabled }} + - name: HEAP_MONITOR_ENABLED + value: "true" + - name: HEAP_MONITOR_THRESHOLD + value: {{ .Values.networkCosts.heapMonitor.threshold }} + {{- if .Values.networkCosts.heapMonitor.outFile }} + - name: HEAP_MONITOR_OUTPUT + value: {{ .Values.networkCosts.heapMonitor.outFile }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.networkCosts.healthCheckProbes }} + {{- toYaml .Values.networkCosts.healthCheckProbes | nindent 8 }} + {{- end }} + volumeMounts: + {{- if .Values.networkCosts.hostProc }} + - mountPath: {{ .Values.networkCosts.hostProc.mountPath }} + name: host-proc + {{- else }} + - mountPath: /net + name: nf-conntrack + - mountPath: /netfilter + name: netfilter + {{- end }} + {{- if .Values.networkCosts.config }} + - mountPath: /network-costs/config + name: network-costs-config + {{- end }} + securityContext: + privileged: true + {{- if .Values.networkCosts.additionalSecurityContext }} + {{- toYaml .Values.networkCosts.additionalSecurityContext | nindent 10 }} + {{- end }} + ports: + - name: http-server + containerPort: {{ .Values.networkCosts.port | default 3001 }} + hostPort: {{ .Values.networkCosts.port | default 3001 }} +{{- if .Values.networkCosts.priorityClassName }} + priorityClassName: "{{ .Values.networkCosts.priorityClassName }}" +{{- end }} + {{- with .Values.networkCosts.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 10 }} + {{- end }} +{{- if .Values.networkCosts.tolerations }} + tolerations: +{{ toYaml .Values.networkCosts.tolerations | indent 8 }} + {{- end }} + {{- with .Values.networkCosts.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- if .Values.networkCosts.config }} + - name: network-costs-config + configMap: + name: network-costs-config + {{- end }} + {{- if .Values.networkCosts.hostProc }} + - name: host-proc + hostPath: + path: {{ default "/proc" .Values.networkCosts.hostProc.hostPath }} + {{- else }} + - name: nf-conntrack + hostPath: + path: /proc/net + - name: netfilter + hostPath: + path: /proc/sys/net/netfilter + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy-template.yaml new file mode 100644 index 0000000000..812956f415 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy-template.yaml @@ -0,0 +1,47 @@ +{{- if .Values.networkPolicy -}} +{{- if .Values.networkPolicy.costAnalyzer.enabled -}} +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }} +{{- if .Values.networkPolicy.costAnalyzer.annotations }} + annotations: +{{ toYaml .Values.networkPolicy.costAnalyzer.annotations | indent 4}} +{{- end }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.networkPolicy.costAnalyzer.additionalLabels }} +{{ toYaml .Values.networkPolicy.costAnalyzer.additionalLabels | indent 4 }} +{{- end }} +spec: + podSelector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 6 }} + policyTypes: +{{- if .Values.networkPolicy.costAnalyzer.ingressRules }} + - Ingress +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.egressRules }} + - Egress +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.egressRules }} + egress: +{{- range $rule := .Values.networkPolicy.costAnalyzer.egressRules }} + - to: +{{ toYaml $rule.selectors | indent 7 }} + ports: +{{ toYaml $rule.ports | indent 9 }} +{{- end }} +{{- end }} +{{- if .Values.networkPolicy.costAnalyzer.ingressRules }} + ingress: +{{- range $rule := .Values.networkPolicy.costAnalyzer.ingressRules }} + - from: +{{ toYaml $rule.selectors | indent 7 }} + ports: +{{ toYaml $rule.ports | indent 9 }} +{{- end }} +{{- end }} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy.yaml new file mode 100644 index 0000000000..77a062e9f4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-network-policy.yaml @@ -0,0 +1,48 @@ +{{- if .Values.networkPolicy -}} +{{- if .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +{{- if .Values.networkPolicy.denyEgress }} +metadata: + name: deny-egress + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "cost-analyzer.selectorLabels" . | nindent 6 }} + policyTypes: + - Egress +{{- else }} +{{- if .Values.networkPolicy.sameNamespace}} +metadata: + name: shared-namespace + namespace: {{ default "kubecost" .Values.networkPolicy.namespace}} +spec: + podSelector: + matchLabels: + app: prometheus + component: server +{{- else }} +metadata: + name: closed-traffic + namespace: {{ default "kubecost" .Values.networkPolicy.namespace}} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: cost-analyzer +{{- end }} + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: cost-analyzer + - namespaceSelector: + matchLabels: + name: k8s-kubecost +{{- end }} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-networks-costs-ocp-scc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-networks-costs-ocp-scc.yaml new file mode 100644 index 0000000000..8602cb0c61 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-networks-costs-ocp-scc.yaml @@ -0,0 +1,30 @@ +{{- if and (.Capabilities.APIVersions.Has "security.openshift.io/v1/SecurityContextConstraints") (.Values.global.platforms.openshift.scc.networkCosts) (.Values.networkCosts.enabled) }} +apiVersion: security.openshift.io/v1 +kind: SecurityContextConstraints +metadata: + name: {{ template "cost-analyzer.networkCostsName" . }} +priority: 10 +allowPrivilegedContainer: true +allowHostDirVolumePlugin: true +allowHostNetwork: true +allowHostPorts: true +allowHostPID: false +allowHostIPC: false +readOnlyRootFilesystem: false +runAsUser: + type: RunAsAny +fsGroup: + type: RunAsAny +seLinuxContext: + type: RunAsAny +supplementalGroups: + type: RunAsAny +seccompProfiles: +- runtime/default +volumes: + - hostPath + - projected + - configMap +users: + - system:serviceaccount:{{ .Release.Namespace }}:{{ template "cost-analyzer.serviceAccountName" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ocp-route.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ocp-route.yaml new file mode 100644 index 0000000000..3438dcd546 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-ocp-route.yaml @@ -0,0 +1,25 @@ +{{- if and (.Capabilities.APIVersions.Has "route.openshift.io/v1/Route") (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.route.enabled) }} +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: {{ template "cost-analyzer.fullname" . }}-route + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.platforms.openshift.route.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.global.platforms.openshift.route.host }} + host: "{{ .Values.global.platforms.openshift.route.host }}" + {{- end }} + port: + targetPort: tcp-frontend + tls: + termination: edge + to: + kind: Service + name: {{ template "cost-analyzer.serviceName" . }} + weight: 100 + wildcardPolicy: None +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-oidc-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-oidc-config-map-template.yaml new file mode 100644 index 0000000000..cb44943d35 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-oidc-config-map-template.yaml @@ -0,0 +1,49 @@ +{{- if .Values.oidc }} +{{- if .Values.oidc.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-oidc + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- $root := . }} + oidc.json: |- + { + "enabled" : {{ .Values.oidc.enabled }}, + "useIDToken" : {{ .Values.oidc.useIDToken | default "false" }}, + "clientID" : "{{ .Values.oidc.clientID }}", + {{- if .Values.oidc.existingCustomSecret.enabled }} + "secretName" : "{{ .Values.oidc.existingCustomSecret.name }}", + {{- else }} + "secretName" : "{{ .Values.oidc.secretName }}", + {{- end }} + "authURL" : "{{ .Values.oidc.authURL }}", + "loginRedirectURL" : "{{ .Values.oidc.loginRedirectURL }}", + "discoveryURL" : "{{ .Values.oidc.discoveryURL }}", + "hostedDomain" : "{{ .Values.oidc.hostedDomain }}", + "skipOnlineTokenValidation" : "{{ .Values.oidc.skipOnlineTokenValidation | default "false" }}", + "useClientSecretPost": {{ .Values.oidc.useClientSecretPost }}, + "rbac" : { + "enabled" : {{ .Values.oidc.rbac.enabled }}, + "groups" : [ + {{- range $i, $g := .Values.oidc.rbac.groups }} + {{- if ne $i 0 }},{{- end }} + { + "roleName": "{{ $g.name }}", + "enabled": {{ $g.enabled }}, + "claimName": "{{ $g.claimName }}", + "claimValues": [ + {{- range $j, $v := $g.claimValues }} + {{- if ne $j 0 }},{{- end }} + "{{ $v }}" + {{- end }} + ] + } + {{- end }} + ] + } + } +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pkey-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pkey-configmap.yaml new file mode 100644 index 0000000000..6420ac75a7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pkey-configmap.yaml @@ -0,0 +1,23 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.productKey }} +{{- if .Values.kubecostProductConfigs.productKey.enabled }} +# If the productKey.key is not specified, the configmap will not be created +{{- if .Values.kubecostProductConfigs.productKey.key }} +# If the secretname is specified, the configmap will not be created +{{- if not .Values.kubecostProductConfigs.productKey.secretname }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "product-configs" .Values.productConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.kubecostProductConfigs.productKey.key }} + key: {{ .Values.kubecostProductConfigs.productKey.key | quote }} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pricing-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pricing-configmap.yaml new file mode 100644 index 0000000000..1325d4434d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pricing-configmap.yaml @@ -0,0 +1,141 @@ +{{- if .Values.kubecostProductConfigs }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "pricing-configs" .Values.pricingConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- if .Values.kubecostProductConfigs.defaultModelPricing }} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.enabled }} + {{- if .Values.kubecostProductConfigs.customPricesEnabled }} + customPricesEnabled: "{{ .Values.kubecostProductConfigs.customPricesEnabled }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.CPU }} + CPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.CPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotCPU }} + spotCPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotCPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.RAM }} + RAM: "{{ .Values.kubecostProductConfigs.defaultModelPricing.RAM | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotRAM }} + spotRAM: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotRAM | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.GPU }} + GPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.GPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.spotGPU }} + spotGPU: "{{ .Values.kubecostProductConfigs.defaultModelPricing.spotGPU | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.storage }} + storage: "{{ .Values.kubecostProductConfigs.defaultModelPricing.storage | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.zoneNetworkEgress }} + zoneNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.zoneNetworkEgress | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.regionNetworkEgress }} + regionNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.regionNetworkEgress | toString }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultModelPricing.internetNetworkEgress }} + internetNetworkEgress: "{{ .Values.kubecostProductConfigs.defaultModelPricing.internetNetworkEgress | toString }}" + {{- end -}} + {{- end -}} + {{- end -}} + {{- if .Values.kubecostProductConfigs.clusterName }} + clusterName: "{{ .Values.kubecostProductConfigs.clusterName }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.clusterAccountID }} + clusterAccountID: "{{ .Values.kubecostProductConfigs.clusterAccountID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.currencyCode }} + currencyCode: "{{ .Values.kubecostProductConfigs.currencyCode }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureBillingRegion }} + azureBillingRegion: "{{ .Values.kubecostProductConfigs.azureBillingRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureSubscriptionID }} + azureSubscriptionID: "{{ .Values.kubecostProductConfigs.azureSubscriptionID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureClientID }} + azureClientID: "{{ .Values.kubecostProductConfigs.azureClientID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureTenantID }} + azureTenantID: "{{ .Values.kubecostProductConfigs.azureTenantID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.azureOfferDurableID }} + azureOfferDurableID: "{{ .Values.kubecostProductConfigs.azureOfferDurableID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.discount }} + discount: "{{ .Values.kubecostProductConfigs.discount }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.negotiatedDiscount }} + negotiatedDiscount: "{{ .Values.kubecostProductConfigs.negotiatedDiscount }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.defaultIdle }} + defaultIdle: "{{ .Values.kubecostProductConfigs.defaultIdle }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.sharedNamespaces }} + sharedNamespaces: "{{ .Values.kubecostProductConfigs.sharedNamespaces }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.sharedOverhead }} + sharedOverhead: "{{ .Values.kubecostProductConfigs.sharedOverhead }}" + {{- end -}} + {{- if gt (len (toString .Values.kubecostProductConfigs.shareTenancyCosts)) 0 }} + {{- if eq (toString .Values.kubecostProductConfigs.shareTenancyCosts) "false" }} + shareTenancyCosts: "false" + {{- else if eq (toString .Values.kubecostProductConfigs.shareTenancyCosts) "true" }} + shareTenancyCosts: "true" + {{- end -}} + {{- end -}} + {{- if .Values.kubecostProductConfigs.spotLabel }} + spotLabel: "{{ .Values.kubecostProductConfigs.spotLabel }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.spotLabelValue }} + spotLabelValue: "{{ .Values.kubecostProductConfigs.spotLabelValue }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataRegion }} + spotDataRegion: "{{ .Values.kubecostProductConfigs.awsSpotDataRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataBucket }} + spotDataBucket: "{{ .Values.kubecostProductConfigs.awsSpotDataBucket }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.awsSpotDataPrefix }} + spotDataPrefix: "{{ .Values.kubecostProductConfigs.awsSpotDataPrefix }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.projectID }} + projectID: "{{ .Values.kubecostProductConfigs.projectID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.bigQueryBillingDataDataset }} + billingDataDataset: "{{ .Values.kubecostProductConfigs.bigQueryBillingDataDataset }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaProjectID }} + athenaProjectID: "{{ .Values.kubecostProductConfigs.athenaProjectID }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaBucketName }} + athenaBucketName: "{{ .Values.kubecostProductConfigs.athenaBucketName }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaRegion }} + athenaRegion: "{{ .Values.kubecostProductConfigs.athenaRegion }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaDatabase }} + athenaDatabase: "{{ .Values.kubecostProductConfigs.athenaDatabase }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaTable }} + athenaTable: "{{ .Values.kubecostProductConfigs.athenaTable }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.athenaWorkgroup }} + athenaWorkgroup: "{{ .Values.kubecostProductConfigs.athenaWorkgroup }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.masterPayerARN}} + masterPayerARN: "{{ .Values.kubecostProductConfigs.masterPayerARN }}" + {{- end }} + {{- if .Values.kubecostProductConfigs.gpuLabel }} + gpuLabel: "{{ .Values.kubecostProductConfigs.gpuLabel }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.gpuLabelValue }} + gpuLabelValue: "{{ .Values.kubecostProductConfigs.gpuLabelValue }}" + {{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-prometheusrule-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-prometheusrule-template.yaml new file mode 100644 index 0000000000..eba7797f38 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-prometheusrule-template.yaml @@ -0,0 +1,22 @@ +{{- if .Values.prometheus }} +{{- if .Values.prometheus.serverFiles }} +{{- if .Values.prometheus.serverFiles.rules }} +{{- if .Values.prometheusRule }} +{{- if .Values.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.prometheusRule.additionalLabels }} + {{ toYaml .Values.prometheusRule.additionalLabels | nindent 4 }} + {{- end }} +spec: + {{ toYaml .Values.prometheus.serverFiles.rules | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pvc-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pvc-template.yaml new file mode 100644 index 0000000000..82a9cdcd0f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-pvc-template.yaml @@ -0,0 +1,33 @@ +{{- if .Values.persistentVolume -}} +{{- if not .Values.persistentVolume.existingClaim -}} +{{- if .Values.persistentVolume.enabled -}} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.persistentVolume.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.persistentVolume.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - ReadWriteOnce + {{- if .Values.persistentVolume.storageClass }} + storageClassName: {{ .Values.persistentVolume.storageClass }} + {{ end }} + resources: + requests: + {{- if .Values.persistentVolume }} + storage: {{ .Values.persistentVolume.size }} + {{- else }} + storage: 32.0Gi + {{ end }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saml-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saml-config-map-template.yaml new file mode 100644 index 0000000000..3293f25980 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saml-config-map-template.yaml @@ -0,0 +1,14 @@ +{{- if .Values.saml }} +{{- if .Values.saml.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-saml + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- $root := . }} + saml.json: '{{ toJson .Values.saml }}' +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saved-reports-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saved-reports-configmap.yaml new file mode 100644 index 0000000000..285229ab2c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-saved-reports-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.global.savedReports }} +{{- if .Values.global.savedReports.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{default "saved-report-configs" .Values.savedReportConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + saved-reports.json: '{{ toJson .Values.global.savedReports.reports }}' +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-server-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-server-configmap.yaml new file mode 100644 index 0000000000..57038b9cdf --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-server-configmap.yaml @@ -0,0 +1,72 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if or .Values.kubecostProductConfigs.grafanaURL .Values.kubecostProductConfigs.labelMappingConfigs .Values.kubecostProductConfigs.cloudAccountMapping}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "app-configs" .Values.appConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: +{{- if .Values.kubecostProductConfigs.labelMappingConfigs }} +{{- if .Values.kubecostProductConfigs.labelMappingConfigs.enabled }} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.owner_label }} + owner_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.owner_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.team_label }} + team_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.team_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.department_label }} + department_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.department_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.product_label }} + product_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.product_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.environment_label }} + environment_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.environment_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.namespace_external_label }} + namespace_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.namespace_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.cluster_external_label }} + cluster_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.cluster_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.controller_external_label }} + controller_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.controller_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.product_external_label }} + product_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.product_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.service_external_label }} + service_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.service_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.deployment_external_label }} + deployment_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.deployment_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.team_external_label }} + team_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.team_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.environment_external_label }} + environment_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.environment_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.department_external_label }} + department_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.department_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.statefulset_external_label }} + statefulset_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.statefulset_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.daemonset_external_label }} + daemonset_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.daemonset_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.pod_external_label }} + pod_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.pod_external_label }}" + {{- end -}} + {{- if .Values.kubecostProductConfigs.labelMappingConfigs.owner_external_label }} + owner_external_label: "{{ .Values.kubecostProductConfigs.labelMappingConfigs.owner_external_label }}" + {{- end -}} +{{- end -}} +{{- end -}} + {{- if .Values.kubecostProductConfigs.grafanaURL }} + grafanaURL: "{{ .Values.kubecostProductConfigs.grafanaURL }}" + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-account-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-account-template.yaml new file mode 100644 index 0000000000..f2a2cec80a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-account-template.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "cost-analyzer.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-template.yaml new file mode 100644 index 0000000000..82d957fca7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-service-template.yaml @@ -0,0 +1,66 @@ +{{- if and (not .Values.agent) (not .Values.cloudAgent) }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "cost-analyzer.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +spec: + selector: + {{- include "cost-analyzer.selectorLabels" . | nindent 4 }} +{{- if .Values.service -}} +{{- if .Values.service.type }} + type: "{{ .Values.service.type }}" +{{- else }} + type: ClusterIP +{{- end }} +{{- else }} + type: ClusterIP +{{- end }} +{{- if (eq .Values.service.type "LoadBalancer") }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- end }} + ports: + - name: tcp-model + port: 9003 + targetPort: 9003 + {{- with .Values.kubecostModel.extraPorts }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and (.Values.kubecostFrontend.enabled) (not (eq (include "frontend.deployMethod" .) "haMode")) }} + - name: tcp-frontend + {{- if (eq .Values.service.type "NodePort") }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- end }} + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + {{- end }} + {{- if or .Values.saml.enabled .Values.oidc.enabled}} + - name: apiserver + port: 9007 + targetPort: 9007 + {{- end }} +{{- if .Values.service.sessionAffinity.enabled }} + sessionAffinity: ClientIP + {{- if .Values.service.sessionAffinity.timeoutSeconds }} + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.service.sessionAffinity.timeoutSeconds }} + {{- end }} +{{- else }} + sessionAffinity: None +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-servicemonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-servicemonitor-template.yaml new file mode 100644 index 0000000000..fb33792467 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-servicemonitor-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.serviceMonitor }} +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cost-analyzer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.additionalLabels }} + {{ toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-model + honorLabels: true + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{ include "cost-analyzer.selectorLabels" . | nindent 6 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-smtp-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-smtp-configmap.yaml new file mode 100644 index 0000000000..fd00091ce3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/cost-analyzer-smtp-configmap.yaml @@ -0,0 +1,12 @@ + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ default "smtp-configs" .Values.smtpConfigmapName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if (((.Values.kubecostProductConfigs).smtp).config) }} +data: + config: {{ .Values.kubecostProductConfigs.smtp.config | quote }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-deployment.yaml new file mode 100644 index 0000000000..1234fa7bbf --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-deployment.yaml @@ -0,0 +1,177 @@ +{{- if and .Values.diagnostics.enabled .Values.diagnostics.deployment.enabled }} +{{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) -}} + +{{- if eq .Values.prometheus.server.global.external_labels.cluster_id "cluster-one" }} +{{- fail "Error: The 'cluster_id' is set to default 'cluster-one'. Please update so that the diagnostics service can uniquely identify data coming from this cluster." }} +{{- end }} + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "diagnostics.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.diagnostics.deployment.labels }} + {{- toYaml .Values.diagnostics.deployment.labels | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "diagnostics.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "diagnostics.selectorLabels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + # Generates a unique annotation upon each `helm upgrade`, forcing a redeployment + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- with .Values.global.podAnnotations}} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + {{- if .Values.diagnostics.deployment.securityContext }} + securityContext: + {{- toYaml .Values.diagnostics.deployment.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + - name: config-db + {{- /* #TODO: make pv? */}} + emptyDir: {} + containers: + - name: diagnostics + args: ["diagnostics"] + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.diagnostics.deployment.containerSecurityContext }} + securityContext: + {{- toYaml .Values.diagnostics.deployment.containerSecurityContext | nindent 12 }} + {{- else if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: config-db + mountPath: /var/configs/db + readOnly: false + - name: federated-storage-config + mountPath: /var/configs/etl + readOnly: true + env: + {{- if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + - name: FEDERATED_STORE_CONFIG + value: /var/configs/etl/federated-store.yaml + - name: DIAGNOSTICS_RUN_IN_COST_MODEL + value: "false" + - name: DIAGNOSTICS_KUBECOST_FQDN + value: {{ template "cost-analyzer.serviceName" . }} + - name: DIAGNOSTICS_KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: DIAGNOSTICS_PRIMARY + value: {{ quote .Values.diagnostics.primary.enabled }} + - name: DIAGNOSTICS_RETENTION + value: {{ .Values.diagnostics.primary.retention }} + - name: DIAGNOSTICS_PRIMARY_READONLY + value: {{ quote .Values.diagnostics.primary.readonly }} + - name: DIAGNOSTICS_POLLING_INTERVAL + value: {{ .Values.diagnostics.pollingInterval }} + - name: DIAGNOSTICS_KEEP_HISTORY + value: {{ quote .Values.diagnostics.keepDiagnosticHistory }} + - name: DIAGNOSTICS_COLLECT_HELM_VALUES + value: {{ quote .Values.diagnostics.collectHelmValues }} + {{- if .Values.systemProxy.enabled }} + - name: HTTP_PROXY + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: http_proxy + value: {{ .Values.systemProxy.httpProxyUrl }} + - name: HTTPS_PROXY + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: https_proxy + value: {{ .Values.systemProxy.httpsProxyUrl }} + - name: NO_PROXY + value: {{ .Values.systemProxy.noProxy }} + - name: no_proxy + value: {{ .Values.systemProxy.noProxy }} + {{- end }} + {{- range $key, $value := .Values.diagnostics.deployment.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- /* TODO: heatlhcheck that validates the diagnotics pod is healthy */}} + {{- if .Values.diagnostics.primary.enabled}} + readinessProbe: + httpGet: + path: /healthz + port: 9007 + ports: + - name: diagnostics-api + containerPort: 9007 + protocol: TCP + {{- end }} + resources: + {{- toYaml .Values.diagnostics.deployment.resources | nindent 12 }} + {{- with .Values.diagnostics.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.diagnostics.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.diagnostics.deployment.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-service.yaml new file mode 100644 index 0000000000..5c0fdebe8a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/diagnostics-service.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.diagnostics.enabled .Values.diagnostics.deployment.enabled .Values.diagnostics.primary.enabled }} +{{- if (not (empty .Values.kubecostModel.federatedStorageConfigSecret )) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "diagnostics.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} +spec: + ports: + - name: diagnostics-api + protocol: TCP + port: 9007 + targetPort: diagnostics-api + selector: + {{- include "diagnostics.selectorLabels" . | nindent 4 }} + type: ClusterIP +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-deployment.yaml new file mode 100644 index 0000000000..78aeb8ed32 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-deployment.yaml @@ -0,0 +1,123 @@ +{{- if .Values.etlUtils.enabled }} + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "etlUtils.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "etlUtils.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "etlUtils.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "etlUtils.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app: {{ template "etlUtils.name" . }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: Always + volumes: + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: etl-bucket-config + secret: + defaultMode: 420 + secretName: {{ .Values.etlUtils.thanosSourceBucketSecret }} + {{- end }} + {{- if .Values.kubecostModel.federatedStorageConfigSecret }} + - name: federated-storage-config + secret: + defaultMode: 420 + secretName: {{ .Values.kubecostModel.federatedStorageConfigSecret }} + {{- end }} + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + containers: + - name: {{ template "etlUtils.name" . }} + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.openSourceOnly }} + {{- fail "ETL Utils cannot be used with open source only" }} + {{- else if .Values.etlUtils.fullImageName }} + image: {{ .Values.etlUtils.fullImageName }} + {{- else if .Values.kubecostModel.fullImageName }} + image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + readinessProbe: + httpGet: + path: /healthz + port: 9006 + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 200 + livenessProbe: + httpGet: + path: /healthz + port: 9006 + initialDelaySeconds: 10 + periodSeconds: 5 + imagePullPolicy: Always + args: ["etl-utils"] + ports: + - name: api + containerPort: 9006 + protocol: TCP + resources: + {{- toYaml .Values.etlUtils.resources | nindent 12 }} + volumeMounts: + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: etl-bucket-config + mountPath: /var/configs/etl + readOnly: true + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if .Values.etlUtils.thanosSourceBucketSecret }} + - name: ETL_BUCKET_CONFIG + value: "/var/configs/etl/object-store.yaml" + {{- end}} + {{- range $key, $value := .Values.etlUtils.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- with .Values.etlUtils.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.etlUtils.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.etlUtils.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-service.yaml new file mode 100644 index 0000000000..8296d7faaa --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/etl-utils-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.etlUtils.enabled }} + +kind: Service +apiVersion: v1 +metadata: + name: {{ template "etlUtils.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "etlUtils.commonLabels" . | nindent 4 }} +spec: + selector: +{{ include "etlUtils.selectorLabels" . | nindent 4 }} + type: "ClusterIP" + ports: + - name: api + port: 9006 + targetPort: 9006 +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/external-grafana-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/external-grafana-config-map-template.yaml new file mode 100644 index 0000000000..1ac24ee3e2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/external-grafana-config-map-template.yaml @@ -0,0 +1,11 @@ +{{- if eq .Values.global.grafana.proxy false -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: external-grafana-config-map + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + grafanaURL: {{ .Values.global.grafana.scheme | default "http" }}://{{- .Values.global.grafana.domainName }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/extra-manifests.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/extra-manifests.yaml new file mode 100644 index 0000000000..edad397d93 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/extra-manifests.yaml @@ -0,0 +1,8 @@ +{{ range .Values.extraObjects }} +--- +{{- if typeIs "string" . }} + {{- tpl . $ }} +{{- else }} + {{- tpl (toYaml .) $ }} +{{- end }} +{{ end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-deployment.yaml new file mode 100644 index 0000000000..acc8a3c7d9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-deployment.yaml @@ -0,0 +1,145 @@ +{{- if and .Values.forecasting.enabled (not .Values.federatedETL.agentOnly) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "forecasting.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "forecasting.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "forecasting.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/name: forecasting + app.kubernetes.io/instance: {{ .Release.Name }} + app: forecasting + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: false + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + containers: + - name: forecasting + {{- if .Values.forecasting.fullImageName }} + image: {{ .Values.forecasting.fullImageName }} + {{- else }} + image: gcr.io/kubecost1/kubecost-modeling:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.forecasting.readinessProbe.enabled }} + volumeMounts: + - name: tmp + {{- /* In the future, this path should be configurable and not under tmp */}} + mountPath: /tmp + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- if .Values.forecasting.imagePullPolicy }} + imagePullPolicy: {{ .Values.forecasting.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + ports: + - name: tcp-api + containerPort: 5000 + protocol: TCP + {{- with .Values.forecasting.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: CONFIG_PATH + value: /var/configs/ + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + - name: KCM_BASE_URL + value: http://{{ template "aggregator.serviceName" . }}:9008 + {{- else }} + - name: KCM_BASE_URL + value: http://{{ template "aggregator.serviceName" . }}:9004 + {{- end }} + - name: MODEL_STORAGE_PATH + value: "/tmp/localrun/models" + - name: PAGE_ITEM_LIMIT + value: "1000" + {{- range $key, $value := .Values.forecasting.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + readinessProbe: + httpGet: + path: /healthz + port: 5000 + initialDelaySeconds: {{ .Values.forecasting.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.forecasting.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.forecasting.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.forecasting.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 5000 + initialDelaySeconds: {{ .Values.forecasting.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.forecasting.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.forecasting.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.forecasting.priority }} + {{- if .Values.forecasting.priority.enabled }} + {{- if .Values.forecasting.priority.name }} + priorityClassName: {{ .Values.forecasting.priority.name }} + {{- else }} + priorityClassName: {{ template "forecasting.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.forecasting.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.forecasting.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.forecasting.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: tmp + {{- /* + An emptyDir for models is necessary because of the + readOnlyRootFilesystem default In the future, this may optionally be a + PV. To allow Python to auto-detect a temp directory, which the code + currently relies on, we mount it at /tmp. In the future this will be a + configurable path. + */}} + emptyDir: + sizeLimit: 500Mi +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-service.yaml new file mode 100644 index 0000000000..41e69961e3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/forecasting-service.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.forecasting.enabled (not .Values.federatedETL.agentOnly) }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "forecasting.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "forecasting.commonLabels" . | nindent 4 }} +spec: + selector: + {{- include "forecasting.selectorLabels" . | nindent 4 }} + type: ClusterIP + ports: + - name: tcp-api + port: 5000 + targetPort: 5000 +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-deployment-template.yaml new file mode 100644 index 0000000000..8ba47e87e7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-deployment-template.yaml @@ -0,0 +1,218 @@ +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "frontend.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 4 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.annotations }} + annotations: + {{- toYaml .Values.kubecostDeployment.annotations | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.kubecostFrontend.haReplicas | default 2 }} + selector: + matchLabels: + {{- include "frontend.selectorLabels" . | nindent 6 }} + {{- if .Values.kubecostFrontend.deploymentStrategy }} + {{- with .Values.kubecostFrontend.deploymentStrategy }} + strategy: {{ toYaml . | nindent 4 }} + {{- end }} + {{- else }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + {{- end }} + template: + metadata: + labels: + {{/* + Force pod restarts on upgrades to ensure the nginx config is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- include "frontend.selectorLabels" . | nindent 8 }} + {{- if .Values.global.additionalLabels }} + {{- toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- if and .Values.kubecostDeployment .Values.kubecostDeployment.labels }} + {{- toYaml .Values.kubecostDeployment.labels | nindent 8 }} + {{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.global.platforms.openshift.enabled }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + - name: tmp + emptyDir: {} + - name: nginx-conf + configMap: + name: nginx-conf + items: + - key: nginx.conf + path: default.conf + {{- if .Values.global.containerSecuritycontext }} + - name: var-run + emptyDir: {} + - name: cache + emptyDir: {} + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + secret: + secretName : {{ .Values.kubecostFrontend.tls.secretName }} + items: + - key: tls.crt + path: kc.crt + - key: tls.key + path: kc.key + {{- end }} + {{- end }} + {{- if .Values.kubecostAdmissionController }} + {{- if .Values.kubecostAdmissionController.enabled }} + {{- if .Values.kubecostAdmissionController.secretName }} + - name: webhook-server-tls + secret: + secretName: {{ .Values.kubecostAdmissionController.secretName }} + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + {{- end }} + {{- end }} + {{- end }} + containers: + {{- if .Values.kubecostFrontend }} + {{- if .Values.kubecostFrontend.fullImageName }} + - image: {{ .Values.kubecostFrontend.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostFrontend.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/frontend-nightly:latest + {{- else }} + - image: {{ .Values.kubecostFrontend.image }}:prod-{{ $.Chart.AppVersion }} + {{- end }} + {{- else }} + - image: gcr.io/kubecost1/frontend:prod-{{ $.Chart.AppVersion }} + {{- end }} + name: cost-analyzer-frontend + ports: + - name: tcp-frontend + containerPort: 9090 + protocol: TCP + env: + - name: GET_HOSTS_FROM + value: dns + {{- if .Values.kubecostFrontend.extraEnv -}} + {{ toYaml .Values.kubecostFrontend.extraEnv | nindent 12 }} + {{- end }} + {{- if .Values.kubecostFrontend.securityContext }} + securityContext: + {{- toYaml .Values.kubecostFrontend.securityContext | nindent 12 }} + {{- else }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: tmp + mountPath: /tmp + - name: nginx-conf + mountPath: /etc/nginx/conf.d/ + {{- if .Values.global.containerSecuritycontext }} + - mountPath: /var/cache/nginx + name: cache + - mountPath: /var/run + name: var-run + {{- end }} + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + - name: tls + mountPath: /etc/ssl/certs + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.kubecostFrontend.resources | nindent 12 }} + {{- if .Values.kubecostFrontend.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostFrontend.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + {{- if .Values.kubecostFrontend.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /healthz + port: 9090 + initialDelaySeconds: {{ .Values.kubecostFrontend.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.readinessProbe.failureThreshold }} + {{- end }} + {{- if .Values.kubecostFrontend.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /healthz + port: 9090 + initialDelaySeconds: {{ .Values.kubecostFrontend.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.kubecostFrontend.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.kubecostFrontend.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.global.containerSecuritycontext }} + securityContext: + {{- toYaml .Values.global.containerSecuritycontext | nindent 12 }} + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.priority }} + {{- if .Values.priority.enabled }} + {{- if gt (len .Values.priority.name) 0 }} + priorityClassName: {{ .Values.priority.name }} + {{- else }} + priorityClassName: {{ template "cost-analyzer.fullname" . }}-priority + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-service-template.yaml new file mode 100644 index 0000000000..22c2d4fde8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/frontend-service-template.yaml @@ -0,0 +1,53 @@ +{{- if eq (include "frontend.deployMethod" .) "haMode" }} +kind: Service +apiVersion: v1 +metadata: + name: {{ template "frontend.serviceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} +spec: + selector: + {{- include "frontend.selectorLabels" . | nindent 4 }} +{{- if .Values.service -}} +{{- if .Values.service.type }} + type: "{{ .Values.service.type }}" +{{- else }} + type: ClusterIP +{{- end }} +{{- else }} + type: ClusterIP +{{- end }} +{{- if (eq .Values.service.type "LoadBalancer") }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- end }} + ports: + - name: tcp-frontend + {{- if (eq .Values.service.type "NodePort") }} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- end }} + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} +{{- if .Values.service.sessionAffinity.enabled }} + sessionAffinity: ClientIP + {{- if .Values.service.sessionAffinity.timeoutSeconds }} + sessionAffinityConfig: + clientIP: + timeoutSeconds: {{ .Values.service.sessionAffinity.timeoutSeconds }} + {{- end }} +{{- else }} + sessionAffinity: None +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/gcpstore-config-map-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/gcpstore-config-map-template.yaml new file mode 100644 index 0000000000..0c5da0df9a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/gcpstore-config-map-template.yaml @@ -0,0 +1,61 @@ +{{- if .Values.global.gcpstore.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: ubbagent-config +data: + config.yaml: | + # The identity section contains authentication information used + # by the agent. + identities: + - name: gcp + gcp: + # This parameter accepts a base64-encoded JSON service + # account key. The value comes from the reporting secret. + encodedServiceAccountKey: $AGENT_ENCODED_KEY + + # The metrics section defines the metric that will be reported. + # Metric names should match verbatim the identifiers created + # during pricing setup. + metrics: + + - name: commercial_ent_node_hr + type: int + endpoints: + - name: servicecontrol + + # The passthrough marker indicates that no aggregation should + # occur for this metric. Reports received are immediately sent + # to the reporting endpoint. We use passthrough for the + # instance_time metric since reports are generated + # automatically by a heartbeat source defined in a later + # section. + passthrough: {} + + # The endpoints section defines where metering data is ultimately + # sent. Currently supported endpoints include: + # * disk - some directory on the local filesystem + # * servicecontrol - Google Service Control + endpoints: + - name: servicecontrol + servicecontrol: + identity: gcp + # The service name is unique to your application and will be + # provided during onboarding. + serviceName: kubecost-ent.endpoints.kubecost-public.cloud.goog + consumerId: $AGENT_CONSUMER_ID # From the reporting secret. + + + # The sources section lists metric data sources run by the agent + # itself. The currently-supported source is 'heartbeat', which + # sends a defined value to a metric at a defined interval. In + # this example, the heartbeat sends a 60-second value through the + # "instance_time" metric every minute. + sources: + - name: commercial_ent_node_hr_heartbeat + heartbeat: + metric: commercial_ent_node_hr + intervalSeconds: 3600 + value: + int64Value: 1 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrole.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrole.yaml new file mode 100644 index 0000000000..ca1666823d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrole.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.rbac.create }} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} + name: {{ template "grafana.fullname" . }}-clusterrole +{{- if or .Values.grafana.sidecar.dashboards.enabled .Values.grafana.sidecar.datasources.enabled }} +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["configmaps"] + verbs: ["get", "watch", "list"] +{{- else }} +rules: [] +{{- end}} +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrolebinding.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrolebinding.yaml new file mode 100644 index 0000000000..4fc7267f32 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-clusterrolebinding.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.rbac.create }} +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "grafana.fullname" . }}-clusterrolebinding + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +subjects: + - kind: ServiceAccount + name: {{ template "grafana.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ template "grafana.fullname" . }}-clusterrole + apiGroup: rbac.authorization.k8s.io +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap-dashboard-provider.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap-dashboard-provider.yaml new file mode 100644 index 0000000000..78c7717be6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap-dashboard-provider.yaml @@ -0,0 +1,28 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.sidecar.dashboards.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} + name: {{ template "grafana.fullname" . }}-config-dashboards + namespace: {{ .Release.Namespace }} +data: + provider.yaml: |- + apiVersion: 1 + providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + options: + path: {{ .Values.grafana.sidecar.dashboards.folder }} +{{- end}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap.yaml new file mode 100644 index 0000000000..04d6146674 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-configmap.yaml @@ -0,0 +1,90 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: +{{- if .Values.grafana.plugins }} + plugins: {{ join "," .Values.grafana.plugins }} +{{- end }} + grafana.ini: | +{{- range $key, $value := index .Values.grafana "grafana.ini" }} + [{{ $key }}] + {{- range $elem, $elemVal := $value }} + {{ $elem }} = {{ $elemVal }} + {{- end }} +{{- end }} + +{{- if .Values.grafana.datasources }} + {{- range $key, $value := .Values.grafana.datasources }} + {{ $key }}: | +{{ toYaml $value | trim | indent 4 }} + {{- end -}} +{{- end }} +{{- if not .Values.grafana.datasources }} + datasources.yaml: | + apiVersion: 1 + datasources: +{{- if .Values.global.prometheus.enabled }} + - access: proxy + isDefault: true + name: Prometheus + type: prometheus + url: http://{{ template "cost-analyzer.prometheus.server.name" . }}.{{ .Release.Namespace }}.svc + jsonData: + httpMethod: POST + prometheusType: Prometheus + prometheusVersion: 2.35.0 + timeInterval: 1m +{{- else }} + - access: proxy + isDefault: true + name: Prometheus + type: prometheus + url: {{ .Values.global.prometheus.fqdn }} + jsonData: + httpMethod: POST + prometheusType: Prometheus + prometheusVersion: 2.35.0 + timeInterval: 1m +{{- end -}} +{{- end }} +{{- if .Values.grafana.dashboardProviders }} + {{- range $key, $value := .Values.grafana.dashboardProviders }} + {{ $key }}: | +{{ toYaml $value | indent 4 }} + {{- end -}} +{{- end -}} + +{{- if .Values.grafana.dashboards }} + download_dashboards.sh: | + #!/usr/bin/env sh + set -euf + {{- if .Values.grafana.dashboardProviders }} + {{- range $key, $value := .Values.grafana.dashboardProviders }} + {{- range $value.providers }} + mkdir -p {{ .options.path }} + {{- end }} + {{- end }} + {{- end }} + + {{- range $provider, $dashboards := .Values.grafana.dashboards }} + {{- range $key, $value := $dashboards }} + {{- if (or (hasKey $value "gnetId") (hasKey $value "url")) }} + curl -sk \ + --connect-timeout 60 \ + --max-time 60 \ + -H "Accept: application/json" \ + -H "Content-Type: application/json;charset=UTF-8" \ + {{- if $value.url -}}{{ $value.url }}{{- else -}} https://grafana.com/api/dashboards/{{ $value.gnetId }}/revisions/{{- if $value.revision -}}{{ $value.revision }}{{- else -}}1{{- end -}}/download{{- end -}}{{ if $value.datasource }}| sed 's|\"datasource\":[^,]*|\"datasource\": \"{{ $value.datasource }}\"|g'{{ end }} \ + > /var/lib/grafana/dashboards/{{ $provider }}/{{ $key }}.json + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{ end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-attached-disks.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-attached-disks.yaml new file mode 100644 index 0000000000..3809640466 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-attached-disks.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-attached-disk-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + attached-disks.json: |- +{{- .Files.Get "grafana-dashboards/attached-disks.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-metrics-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-metrics-template.yaml new file mode 100644 index 0000000000..729869176d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-metrics-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-cluster-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + cluster-metrics.json: |- +{{- .Files.Get "grafana-dashboards/cluster-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-utilization-template.yaml new file mode 100644 index 0000000000..2cdbd394cd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-cluster-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-cluster-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + cluster-utilization.json: |- +{{- .Files.Get "grafana-dashboards/cluster-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-deployment-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-deployment-utilization-template.yaml new file mode 100644 index 0000000000..f12d1095bd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-deployment-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-deployment-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + deployment-utilization.json: |- +{{- .Files.Get "grafana-dashboards/deployment-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml new file mode 100644 index 0000000000..60ad32d43e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-kubernetes-resource-efficiency-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-kubernetes-resource-efficiency + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + kubernetes-resource-efficiency.json: |- +{{- .Files.Get "grafana-dashboards/kubernetes-resource-efficiency.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-label-cost-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-label-cost-utilization-template.yaml new file mode 100644 index 0000000000..e08092459d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-label-cost-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-label-cost + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + label-cost-utilization.json: |- +{{- .Files.Get "grafana-dashboards/label-cost-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-namespace-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-namespace-utilization-template.yaml new file mode 100644 index 0000000000..f6d28686bb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-namespace-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-namespace-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + namespace-utilization.json: |- +{{- .Files.Get "grafana-dashboards/namespace-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-cloud-sevices.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-cloud-sevices.yaml new file mode 100644 index 0000000000..af72b66644 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-cloud-sevices.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-network-cloud-services + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + grafana-network-cloud-services.json: |- +{{- .Files.Get "grafana-dashboards/network-cloud-services.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-costs.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-costs.yaml new file mode 100644 index 0000000000..2e753745d5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-network-costs.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-network-costs-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + networkCosts-metrics.json: |- +{{- .Files.Get "grafana-dashboards/networkCosts-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-node-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-node-utilization-template.yaml new file mode 100644 index 0000000000..8f2998c257 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-node-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-node-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + node-utilization.json: |- +{{- .Files.Get "grafana-dashboards/node-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml new file mode 100644 index 0000000000..7b8b6ae7ab --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-multi-cluster.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-pod-utilization-multi-cluster + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + pod-utilization-multi-cluster.json: |- +{{- .Files.Get "grafana-dashboards/pod-utilization-multi-cluster.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-template.yaml new file mode 100644 index 0000000000..04374ff43a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-pod-utilization-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-pod-utilization + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + pod-utilization.json: |- +{{- .Files.Get "grafana-dashboards/pod-utilization.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-prometheus-metrics-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-prometheus-metrics-template.yaml new file mode 100644 index 0000000000..723767c976 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-prometheus-metrics-template.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-prom-benchmark + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + prom-benchmark.json: |- +{{- .Files.Get "grafana-dashboards/prom-benchmark.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-aggregator.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-aggregator.yaml new file mode 100644 index 0000000000..40dfb558b2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-aggregator.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-workload-aggregator + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + workload-metrics-aggregator.json: |- +{{- .Files.Get "grafana-dashboards/workload-metrics-aggregator.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-metrics.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-metrics.yaml new file mode 100644 index 0000000000..fa027dce71 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboard-workload-metrics.yaml @@ -0,0 +1,21 @@ +{{- if (((.Values.grafana).sidecar).dashboards).enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboard-workload-metrics + {{- if $.Values.grafana.namespace_dashboards }} + namespace: {{ $.Values.grafana.namespace_dashboards }} + {{- end }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.dashboards.label }} + {{ $.Values.grafana.sidecar.dashboards.label }}: "{{ $.Values.grafana.sidecar.dashboards.labelValue }}" + {{- else }} + grafana_dashboard: "1" + {{- end }} + annotations: +{{- toYaml .Values.grafana.sidecar.dashboards.annotations | nindent 4 }} +data: + grafana-workload-metrics.json: |- +{{- .Files.Get "grafana-dashboards/workload-metrics.json" | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboards-json-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboards-json-configmap.yaml new file mode 100644 index 0000000000..b7ccb3cb54 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-dashboards-json-configmap.yaml @@ -0,0 +1,24 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.dashboards }} + {{- range $provider, $dashboards := .Values.grafana.dashboards }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "grafana.fullname" $ }}-dashboards-{{ $provider }} + namespace: {{ $.Release.Namespace }} + labels: + app: {{ template "grafana.name" $ }} + release: {{ $.Release.Name }} + heritage: {{ $.Release.Service }} + dashboard-provider: {{ $provider }} +data: + {{- range $key, $value := $dashboards }} + {{- if hasKey $value "json" }} + {{ $key }}.json: | +{{ $value.json | indent 4 }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-datasource-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-datasource-template.yaml new file mode 100644 index 0000000000..ba4ecea8c9 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-datasource-template.yaml @@ -0,0 +1,38 @@ +{{- if .Values.grafana -}} +{{- if .Values.grafana.sidecar -}} +{{- if .Values.grafana.sidecar.datasources -}} +{{- if .Values.grafana.sidecar.datasources.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasource + {{- if $.Values.grafana.namespace_datasources }} + namespace: {{ $.Values.grafana.namespace_datasources }} + {{- end }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if $.Values.grafana.sidecar.datasources.label }} + {{ $.Values.grafana.sidecar.datasources.label }}: "1" + {{- else }} + {{- if .Values.global.grafana.enabled }} + kubecost_grafana_datasource: "1" + {{- else }} + grafana_datasource: "1" + {{- end }} + {{- end }} +data: + {{ default "datasource.yaml" .Values.grafana.sidecar.datasources.dataSourceFilename }}: |- + apiVersion: 1 + datasources: + - access: proxy + name: default-kubecost + type: prometheus +{{- if .Values.grafana.sidecar.datasources.defaultDatasourceEnabled }} + isDefault: true +{{- else }} + isDefault: false +{{- end }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-deployment.yaml new file mode 100644 index 0000000000..63598d6dd5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-deployment.yaml @@ -0,0 +1,313 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.grafana.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.grafana.replicas }} + selector: + matchLabels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + strategy: + type: {{ .Values.grafana.deploymentStrategy }} + {{- if ne .Values.grafana.deploymentStrategy "RollingUpdate" }} + rollingUpdate: null + {{- end }} + template: + metadata: + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} + {{- with .Values.grafana.podAnnotations }} + annotations: + {{ toYaml . | indent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "grafana.serviceAccountName" . }} + {{- if .Values.grafana.schedulerName }} + schedulerName: "{{ .Values.grafana.schedulerName }}" + {{- end }} + {{- if .Values.grafana.securityContext }} + securityContext: + {{- toYaml .Values.grafana.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + {{- if .Values.grafana.priorityClassName }} + priorityClassName: "{{ .Values.grafana.priorityClassName }}" + {{- end }} + {{- if .Values.grafana.dashboards }} + initContainers: + - name: download-dashboards + image: "{{ .Values.grafana.downloadDashboardsImage.repository }}:{{ .Values.grafana.downloadDashboardsImage.tag }}" + imagePullPolicy: {{ .Values.grafana.downloadDashboardsImage.pullPolicy }} + command: ["sh", "/etc/grafana/download_dashboards.sh"] + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: "/etc/grafana/download_dashboards.sh" + subPath: download_dashboards.sh + - name: storage + mountPath: "/var/lib/grafana" + {{- if .Values.grafana.persistence.subPath }} + subPath: {{ .Values.grafana.persistence.subPath }} + {{- end }} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + {{- if .Values.grafana.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.grafana.image.pullSecrets }} + - name: {{ . }} + {{- end}} + {{- end }} + containers: + {{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: {{ template "grafana.name" . }}-sc-dashboard + image: "{{ .Values.grafana.sidecar.image.repository }}:{{ .Values.grafana.sidecar.image.tag }}" + imagePullPolicy: {{ .Values.grafana.sidecar.image.pullPolicy }} + {{- if .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml .Values.global.containerSecurityContext | nindent 12 -}} + {{- end }} + env: + - name: LABEL + value: "{{ .Values.grafana.sidecar.dashboards.label }}" + - name: FOLDER + value: "{{ .Values.grafana.sidecar.dashboards.folder }}" + - name: ERROR_THROTTLE_SLEEP + value: "{{ .Values.grafana.sidecar.dashboards.error_throttle_sleep }}" + {{- with .Values.grafana.sidecar.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: sc-dashboard-volume + mountPath: {{ .Values.grafana.sidecar.dashboards.folder | quote }} + {{- end}} + {{- if .Values.grafana.sidecar.datasources.enabled }} + - name: {{ template "grafana.name" . }}-sc-datasources + image: "{{ .Values.grafana.sidecar.image.repository }}:{{ .Values.grafana.sidecar.image.tag }}" + imagePullPolicy: {{ .Values.grafana.sidecar.image.pullPolicy }} + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: LABEL + value: "{{ .Values.grafana.sidecar.datasources.label }}" + - name: FOLDER + value: "/etc/grafana/provisioning/datasources" + - name: ERROR_THROTTLE_SLEEP + value: "{{ .Values.grafana.sidecar.datasources.error_throttle_sleep }}" + resources: + {{ toYaml .Values.grafana.sidecar.resources | indent 12 }} + volumeMounts: + - name: sc-datasources-volume + mountPath: "/etc/grafana/provisioning/datasources" + {{- end}} + - name: grafana + image: "{{ .Values.grafana.image.repository }}:{{ .Values.grafana.image.tag }}" + imagePullPolicy: {{ .Values.grafana.image.pullPolicy }} + {{- with .Values.global.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: "/etc/grafana/grafana.ini" + subPath: grafana.ini + - name: ldap + mountPath: "/etc/grafana/ldap.toml" + subPath: ldap.toml +{{- if .Values.grafana.dashboards }} + {{- range $provider, $dashboards := .Values.grafana.dashboards }} + {{- range $key, $value := $dashboards }} + {{- if hasKey $value "json" }} + - name: dashboards-{{ $provider }} + mountPath: "/var/lib/grafana/dashboards/{{ $provider }}/{{ $key }}.json" + subPath: "{{ $key }}.json" + {{- end }} + {{- end }} + {{- end }} +{{- end -}} +{{- if .Values.grafana.dashboardsConfigMaps }} + {{- range keys .Values.grafana.dashboardsConfigMaps }} + - name: dashboards-{{ . }} + mountPath: "/var/lib/grafana/dashboards/{{ . }}" + {{- end }} +{{- end }} +{{- if or (.Values.grafana.datasources) (include "cost-analyzer.grafanaEnabled" .) }} + - name: config + mountPath: "/etc/grafana/provisioning/datasources/datasources.yaml" + subPath: datasources.yaml +{{- end }} +{{- if .Values.grafana.dashboardProviders }} + - name: config + mountPath: "/etc/grafana/provisioning/dashboards/dashboardproviders.yaml" + subPath: dashboardproviders.yaml +{{- end }} +{{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: sc-dashboard-volume + mountPath: {{ .Values.grafana.sidecar.dashboards.folder | quote }} + - name: sc-dashboard-provider + mountPath: "/etc/grafana/provisioning/dashboards/sc-dashboardproviders.yaml" + subPath: provider.yaml +{{- end}} +{{- if .Values.grafana.sidecar.datasources.enabled }} + - name: sc-datasources-volume + mountPath: "/etc/grafana/provisioning/datasources" +{{- end}} + - name: storage + mountPath: "/var/lib/grafana" + {{- if .Values.grafana.persistence.subPath }} + subPath: {{ .Values.grafana.persistence.subPath }} + {{- end }} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + ports: + - name: service + containerPort: {{ .Values.grafana.service.port }} + protocol: TCP + - name: grafana + containerPort: 3000 + protocol: TCP + env: + - name: GF_SECURITY_ADMIN_USER + valueFrom: + secretKeyRef: + name: {{ template "grafana.fullname" . }} + key: admin-user + - name: GF_SECURITY_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "grafana.fullname" . }} + key: admin-password + {{- if .Values.grafana.plugins }} + - name: GF_INSTALL_PLUGINS + valueFrom: + configMapKeyRef: + name: {{ template "grafana.fullname" . }} + key: plugins + {{- end }} + {{- if .Values.grafana.smtp.existingSecret }} + - name: GF_SMTP_USER + valueFrom: + secretKeyRef: + name: {{ .Values.grafana.smtp.existingSecret }} + key: user + - name: GF_SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.grafana.smtp.existingSecret }} + key: password + {{- end }} +{{- range $key, $value := .Values.grafana.env }} + - name: "{{ $key }}" + value: "{{ $value }}" +{{- end }} + {{- if .Values.grafana.envFromSecret }} + envFrom: + - secretRef: + name: {{ .Values.grafana.envFromSecret }} + {{- end }} + livenessProbe: +{{ toYaml .Values.grafana.livenessProbe | indent 12 }} + readinessProbe: +{{ toYaml .Values.grafana.readinessProbe | indent 12 }} + resources: +{{ toYaml .Values.grafana.resources | indent 12 }} + {{- with .Values.grafana.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.grafana.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.grafana.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ template "grafana.fullname" . }} + {{- if .Values.grafana.dashboards }} + {{- range keys .Values.grafana.dashboards }} + - name: dashboards-{{ . }} + configMap: + name: {{ template "grafana.fullname" $ }}-dashboards-{{ . }} + {{- end }} + {{- end }} + {{- if .Values.grafana.dashboardsConfigMaps }} + {{- range $provider, $name := .Values.grafana.dashboardsConfigMaps }} + - name: dashboards-{{ $provider }} + configMap: + name: {{ $name }} + {{- end }} + {{- end }} + - name: ldap + secret: + {{- if .Values.grafana.ldap.existingSecret }} + secretName: {{ .Values.grafana.ldap.existingSecret }} + {{- else }} + secretName: {{ template "grafana.fullname" . }} + {{- end }} + items: + - key: ldap-toml + path: ldap.toml + - name: storage + {{- if .Values.grafana.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.grafana.persistence.existingClaim | default (include "grafana.fullname" .) }} + {{- else }} + emptyDir: {} + {{- end -}} + {{- if .Values.grafana.sidecar.dashboards.enabled }} + - name: sc-dashboard-volume + emptyDir: {} + - name: sc-dashboard-provider + configMap: + name: {{ template "grafana.fullname" . }}-config-dashboards + {{- end }} + {{- if .Values.grafana.sidecar.datasources.enabled }} + - name: sc-datasources-volume + emptyDir: {} + {{- end -}} + {{- range .Values.grafana.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + defaultMode: {{ .defaultMode }} + {{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-ingress.yaml new file mode 100644 index 0000000000..da2038170f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-ingress.yaml @@ -0,0 +1,47 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.ingress.enabled -}} +{{- $fullName := include "grafana.fullname" . -}} +{{- $servicePort := .Values.service.port -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.grafana.ingress.labels }} +{{ toYaml .Values.grafana.ingress.labels | indent 4 }} +{{- end }} +{{- with .Values.grafana.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.grafana.ingress.tls }} + tls: + {{- range .Values.grafana.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.grafana.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + pathType: {{ $.Values.grafana.ingress.pathType }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $servicePort }} + {{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-pvc.yaml new file mode 100644 index 0000000000..d90e7f7477 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-pvc.yaml @@ -0,0 +1,26 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if and .Values.grafana.persistence.enabled (not .Values.grafana.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.grafana.persistence.annotations }} + annotations: +{{ toYaml . | indent 4 }} + {{- end }} +spec: + accessModes: + {{- range .Values.grafana.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.grafana.persistence.size | quote }} + storageClassName: {{ .Values.grafana.persistence.storageClassName }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-secret.yaml new file mode 100644 index 0000000000..df8b46ddeb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-secret.yaml @@ -0,0 +1,22 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: + admin-user: {{ .Values.grafana.adminUser | b64enc | quote }} + {{- if .Values.grafana.adminPassword }} + admin-password: {{ .Values.grafana.adminPassword | b64enc | quote }} + {{- else }} + admin-password: {{ randAlphaNum 40 | b64enc | quote }} + {{- end }} + {{- if not .Values.grafana.ldap.existingSecret }} + ldap-toml: {{ .Values.grafana.ldap.config | b64enc | quote }} + {{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-service.yaml new file mode 100644 index 0000000000..3bf668ed8a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-service.yaml @@ -0,0 +1,51 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "grafana.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.grafana.service.labels }} +{{ toYaml .Values.grafana.service.labels | indent 4 }} +{{- end }} +{{- with .Values.grafana.service.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if (or (eq .Values.grafana.service.type "ClusterIP") (empty .Values.grafana.service.type)) }} + type: ClusterIP + {{- if .Values.grafana.service.clusterIP }} + clusterIP: {{ .Values.grafana.service.clusterIP }} + {{end}} +{{- else if eq .Values.grafana.service.type "LoadBalancer" }} + type: {{ .Values.grafana.service.type }} + {{- if .Values.grafana.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.grafana.service.loadBalancerIP }} + {{- end }} + {{- if .Values.grafana.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.grafana.service.loadBalancerSourceRanges | indent 4 }} + {{- end -}} +{{- else }} + type: {{ .Values.grafana.service.type }} +{{- end }} +{{- if .Values.grafana.service.externalIPs }} + externalIPs: +{{ toYaml .Values.grafana.service.externalIPs | indent 4 }} +{{- end }} + ports: + - name: tcp-service + port: {{ .Values.grafana.service.port }} + protocol: TCP + targetPort: 3000 +{{ if (and (eq .Values.grafana.service.type "NodePort") (not (empty .Values.grafana.service.nodePort))) }} + nodePort: {{.Values.grafana.service.nodePort}} +{{ end }} + selector: + app: {{ template "grafana.name" . }} + release: {{ .Release.Name }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-serviceaccount.yaml new file mode 100644 index 0000000000..bf2f21db6e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/grafana-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if (eq (include "cost-analyzer.grafanaEnabled" .) "true") }} +{{- if .Values.grafana.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: {{ template "grafana.name" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "grafana.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/install-plugins.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/install-plugins.yaml new file mode 100644 index 0000000000..f2abf1c416 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/install-plugins.yaml @@ -0,0 +1,43 @@ +{{- if .Values.kubecostModel.plugins.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-install-plugins + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + install_plugins.sh: |- + {{- if .Values.kubecostModel.plugins.install.enabled }} + set -ex + rm -f {{ .Values.kubecostModel.plugins.folder }}/bin/* + mkdir -p {{ .Values.kubecostModel.plugins.folder }}/bin + cd {{ .Values.kubecostModel.plugins.folder }}/bin + OSTYPE=$(cat /etc/os-release) + OS='' + case "$OSTYPE" in + *Linux*) OS='linux';; + *) echo "$OSTYPE is unsupported" && exit 1 ;; + esac + + UNAME_OUTPUT=$(uname -m) + ARCH='' + case "$UNAME_OUTPUT" in + *x86_64*) ARCH='amd64';; + *amd64*) ARCH='amd64';; + *aarch64*) ARCH='arm64';; + *arm64*) ARCH='arm64';; + *) echo "$UNAME_OUTPUT is unsupported" && exit 1 ;; + esac + + {{- if .Values.kubecostModel.plugins.version }} + VER={{ .Values.kubecostModel.plugins.version | quote}} + {{- else }} + VER=$(curl --silent https://api.github.com/repos/opencost/opencost-plugins/releases/latest | grep ".tag_name" | awk -F\" '{print $4}') + {{- end }} + + {{- range $pluginName := .Values.kubecostModel.plugins.enabledPlugins }} + curl -fsSLO "https://github.com/opencost/opencost-plugins/releases/download/$VER/{{ $pluginName }}.ocplugin.$OS.$ARCH" + chmod a+rx "{{ $pluginName }}.ocplugin.$OS.$ARCH" + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-queries-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-queries-configmap.yaml new file mode 100644 index 0000000000..5e0af3e00d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-queries-configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.global.integrations.postgres.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: kubecost-integrations-postgres-queries + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + kubecost-queries.json: |- + {{- with .Values.global.integrations.postgres.queryConfigs }} + {{- . | toJson | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-secret.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-secret.yaml new file mode 100644 index 0000000000..136ab60163 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/integrations-postgres-secret.yaml @@ -0,0 +1,19 @@ +{{- if and (.Values.global.integrations.postgres.enabled) (eq .Values.global.integrations.postgres.databaseSecretName "") }} +apiVersion: v1 +kind: Secret +metadata: + name: kubecost-integrations-postgres + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +type: Opaque +stringData: + creds.json: |- + { + "host": "{{ .Values.global.integrations.postgres.databaseHost }}", + "port": "{{ .Values.global.integrations.postgres.databasePort }}", + "databaseName": "{{ .Values.global.integrations.postgres.databaseName }}", + "user": "{{ .Values.global.integrations.postgres.databaseUser }}", + "password": "{{ .Values.global.integrations.postgres.databasePassword }}" + } +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-service-template.yaml new file mode 100644 index 0000000000..658dca3a96 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-service-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.kubecostAdmissionController -}} +{{- if .Values.kubecostAdmissionController.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: webhook-server + namespace: {{.Release.Namespace}} +spec: + selector: + {{ include "cost-analyzer.selectorLabels" . | nindent 4 }} + ports: + - port: 443 + targetPort: 8443 +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-template.yaml new file mode 100644 index 0000000000..be68bcea14 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-admission-controller-template.yaml @@ -0,0 +1,30 @@ +{{- if .Values.kubecostAdmissionController -}} +{{- if .Values.kubecostAdmissionController.enabled -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: kubecost-deployment-validation +webhooks: + - name: "kubecost-deployment-validation.kubecost.svc" + failurePolicy: Ignore + rules: + - operations: [ "CREATE", "UPDATE" ] + apiGroups: [ "apps" ] + apiVersions: [ "v1" ] + resources: [ "deployments" ] + scope: "*" + clientConfig: + service: + namespace: {{.Release.Namespace}} + name: webhook-server + path: "/validate" + {{- if .Values.kubecostAdmissionController.caBundle }} + caBundle: {{ .Values.kubecostAdmissionController.caBundle }} + {{- else }} + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCRENDQWV5Z0F3SUJBZ0lVR3E2YkdOaEowVjRsb0NiWHhUa0pocWkwUnB3d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0pqRWtNQ0lHQTFVRUF3d2JkMlZpYUc5dmF5MXpaWEoyWlhJdWEzVmlaV052YzNRdWMzWmpNQjRYRFRJegpNREl3T1RFNU1UVTFNbG9YRFRJME1EWXlNekU1TVRVMU1sb3dKakVrTUNJR0ExVUVBd3diZDJWaWFHOXZheTF6ClpYSjJaWEl1YTNWaVpXTnZjM1F1YzNaak1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXpvU2JBejBhZFJTdEN3eVRPSGd2S2VuQ29GbWE2OC9nYTFHZjVST2dXeGJhamhQRTZKbEtBcENwK1pzKwo2bHJzL2J3bkx5SDdoMUFJa1NmZ25EYlNadDJjdHRFSmhSd25vKy90WElMYk84WndRQTErYXpUQzVtSkluZVF3CktRMkErYy9CUnk3N3B0SnZIRStkTEllcWhRelV2M25nWUwvSDZaMUZPa20xUCtlR0FwSWxyVHVPV1ozUVhRYkMKemhOQXppRWNjL3o3RERBdlFBMlpIQ1I2OGl1V0ptd0RYZEdjWmEwenNVb1hDbGIvWXdiWFgvMlp2dklIbkdtawp5VTlZdEhxNVpscFZjT0V5MTVBWFVEOFZVUU1jVXQ5NkJvVThMMXJKbTZJK0E0YmFySEs5QjlxcjdzRmFaY2wvCnBncHZGd0NBaHZHYUM2VzA5UnM3T0NrdXh3SURBUUFCb3lvd0tEQW1CZ05WSFJFRUh6QWRnaHQzWldKb2IyOXIKTFhObGNuWmxjaTVyZFdKbFkyOXpkQzV6ZG1Nd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDdVhNcUgzYmhsVApGKzlRUFplS2xiUTZlWSs0NlhMVGtEdlZzenAyZysweWhlMVNRRHZRUTVad1l6MnMwODNqb2loTXVzeFZ1TmFGCk1LdE9vbGY2bitsaUZFcEw4OU9XZ1VjdzJRdFdqVWUraU1zby91dWN0eGVPTzZLam9JcUVrUlg5YXh1cGxxVm0KakZRaGZtNlRYZ2pxWmttUVNsbHdLVkcxSFJZTkRveFpFa0JHK1l6RWF5QmdQdXl4bW5iTDdlck5IOVJQSVZtbAoxaWFnS1NVVG5vN0hJY3IwdHYzT3JEWDZRN3VJUGdWanBRSHMzNXBZSWlBYjVNR0RjWFZvY050SEZ0YnluREhzCi80WGhYMjFhOXdnSVF6dUF3ck0zQ0VDRnVocHJzWlZmQjBKQ1dBOG1aVEZneTVBL0tLUjJmTXRMRWRQS1ZsSXUKZjc1MjB3T3JzME09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + {{- end }} + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secret-template.yaml new file mode 100644 index 0000000000..cda3c60559 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secret-template.yaml @@ -0,0 +1,12 @@ +{{- if .Values.agentKey }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.agentKeySecretName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + object-store.yaml: {{ .Values.agentKey }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secretprovider-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secretprovider-template.yaml new file mode 100644 index 0000000000..3ebc1a4b67 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-agent-secretprovider-template.yaml @@ -0,0 +1,25 @@ +{{- if .Values.agent }} +{{- if ((.Values.agentCsi).enabled) }} +{{- if .Capabilities.APIVersions.Has "secrets-store.csi.x-k8s.io/v1" }} +apiVersion: secrets-store.csi.x-k8s.io/v1 +{{- else }} +apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 +{{- end }} +kind: SecretProviderClass +metadata: + name: {{ .Values.agentCsi.secretProvider.name }} + namespace: {{ .Release.Namespace }} + labels: {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} +spec: + provider: {{ required "Specify a valid provider." .Values.agentCsi.secretProvider.provider }} + {{- if .Values.agentCsi.secretProvider.parameters }} + parameters: + {{- .Values.agentCsi.secretProvider.parameters | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.agentCsi.secretProvider.secretObjects }} + secretObjects: + {{- .Values.agentCsi.secretProvider.secretObjects | toYaml | nindent 2 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-actions-config.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-actions-config.yaml new file mode 100644 index 0000000000..114f381b02 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-actions-config.yaml @@ -0,0 +1,56 @@ +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-continuous-cluster-sizing + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.clusterRightsize }} +binaryData: + config: | +{{- toJson .Values.clusterController.actionConfigs.clusterRightsize | b64enc | nindent 4 }} +{{- end }} +--- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-nsturndown-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.namespaceTurndown }} +binaryData: +{{- range .Values.clusterController.actionConfigs.namespaceTurndown }} + {{ .name }}: | + {{- toJson . | b64enc | nindent 4 }} +{{- end }} +{{- end }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-controller-container-rightsizing-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +{{- if .Values.clusterController.actionConfigs.containerRightsize }} +binaryData: + config: | +{{- toJson .Values.clusterController.actionConfigs.containerRightsize | b64enc | nindent 4 }} +{{- end }} +{{- range .Values.clusterController.actionConfigs.clusterTurndown }} +--- +apiVersion: kubecost.com/v1alpha1 +kind: TurndownSchedule +metadata: + name: {{ .name }} +spec: + start: {{ .start }} + end: {{ .end }} + repeat: {{ .repeat }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-template.yaml new file mode 100644 index 0000000000..ac86658be5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-controller-template.yaml @@ -0,0 +1,293 @@ +{{- if .Values.clusterController }} +{{- if .Values.clusterController.enabled }} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +--- +# +# NOTE: +# The following ClusterRole permissions are only created and assigned for the +# cluster controller feature. They will not be added to any clusters by default. +# +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +rules: + - apiGroups: + - kubecost.com + resources: + - turndownschedules + - turndownschedules/status + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - '' + - events.k8s.io + resources: + - events + verbs: + - create + - patch + - update + - apiGroups: + - '' + resources: + - deployments + - nodes + - pods + - resourcequotas + - replicationcontrollers + - limitranges + - pods/eviction + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - update + - patch + - delete + - apiGroups: + - '' + resources: + - configmaps + - namespaces + - persistentvolumeclaims + - persistentvolumes + - endpoints + - events + - services + verbs: + - get + - list + - watch + - apiGroups: + - '' + resources: + - configmaps + resourceNames: + - 'cluster-controller-nsturndown-config' + verbs: + - get + - create + - update + - apiGroups: + - apps + resources: + - statefulsets + - deployments + - daemonsets + - replicasets + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - list + - watch + - create + - patch + - update + - delete + - apiGroups: + - autoscaling + resources: + - horizontalpodautoscalers + verbs: + - get + - list + - watch + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - get + - list + - watch + # Used for namespace turndown + # When cleaning a namespace, we need the ability to remove + # arbitrary resources (since we helm uninstall all releases in that NS first) + {{- if .Values.clusterController.namespaceTurndown.rbac.enabled }} + - apiGroups: ["*"] + resources: ["*"] + verbs: + - list + - get + - delete + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "kubecost.clusterControllerName" . }} +subjects: + - kind: ServiceAccount + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kubecost.clusterControllerName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + selector: + matchLabels: + app: {{ template "kubecost.clusterControllerName" . }} + template: + metadata: + labels: + app: {{ template "kubecost.clusterControllerName" . }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.clusterController.priorityClassName }} + priorityClassName: "{{ .Values.clusterController.priorityClassName }}" + {{- end }} + containers: + - name: {{ template "kubecost.clusterControllerName" . }} + {{- if eq (typeOf .Values.clusterController.image) "string" }} + image: {{ .Values.clusterController.image }} + {{- else }} + image: {{ .Values.clusterController.image.repository }}:{{ .Values.clusterController.image.tag }} + {{- end}} + imagePullPolicy: {{ .Values.clusterController.imagePullPolicy }} + volumeMounts: + - name: cluster-controller-keys + mountPath: /var/keys + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + - name: TURNDOWN_NAMESPACE + value: {{ .Release.Namespace }} + - name: TURNDOWN_DEPLOYMENT + value: {{ template "kubecost.clusterControllerName" . }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/keys/service-key.json + - name: CC_LOG_LEVEL + value: {{ .Values.clusterController.logLevel | default "info" }} + - name: CC_KUBESCALER_COST_MODEL_PATH + value: http://{{ $serviceName }}.{{ .Release.Namespace }}:{{ .Values.service.targetPort | default 9090 }}/model + - name: CC_CCL_COST_MODEL_PATH + value: http://{{ $serviceName }}.{{ .Release.Namespace }}:{{ .Values.service.targetPort | default 9090 }}/model + {{- if .Values.clusterController.kubescaler }} + - name: CC_KUBESCALER_DEFAULT_RESIZE_ALL + value: {{ .Values.clusterController.kubescaler.defaultResizeAll | default "false" | quote }} + {{- end }} + ports: + - name: http-server + containerPort: 9731 + hostPort: 9731 + serviceAccount: {{ template "kubecost.clusterControllerName" . }} + serviceAccountName: {{ template "kubecost.clusterControllerName" . }} + {{- with .Values.clusterController.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: cluster-controller-keys + secret: + secretName: {{ .Values.clusterController.secretName | default "cluster-controller-service-key" }} + # The secret is optional because not all of cluster controller's + # functionality requires this secret. Cluster controller will + # partially or fully initialize based on the presence of these keys + # and their validity. + optional: true +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kubecost.clusterControllerName" . }}-service + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - name: http + protocol: TCP + port: 9731 + targetPort: 9731 + selector: + app: {{ template "kubecost.clusterControllerName" . }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-manager-configmap-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-manager-configmap-template.yaml new file mode 100644 index 0000000000..b851fd4e93 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-cluster-manager-configmap-template.yaml @@ -0,0 +1,14 @@ +{{- if .Values.kubecostProductConfigs }} +{{- if .Values.kubecostProductConfigs.clusters }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: kubecost-clusters + namespace: {{ .Release.Namespace }} + labels: + {{- include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + default-clusters.yaml: | +{{- toYaml .Values.kubecostProductConfigs.clusters | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-deployment-template.yaml new file mode 100644 index 0000000000..e93ae0f0d1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-deployment-template.yaml @@ -0,0 +1,341 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if or (or .Values.kubecostMetrics.exporter.enabled .Values.agent) .Values.cloudAgent }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.kubecostMetrics.exporter.labels }} +{{ toYaml . | indent 4 }} +{{- end }} +spec: + replicas: {{ .Values.kubecostMetrics.exporter.replicas | default 1 }} + selector: + matchLabels: + app: {{ include "kubecost.kubeMetricsName" . }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: {{ template "kubecost.kubeMetricsName" . }} + {{- if .Values.global.additionalLabels }} + {{ toYaml .Values.global.additionalLabels | nindent 8 }} + {{- end }} +{{- with .Values.kubecostMetrics.exporter.labels }} +{{ toYaml . | indent 8 }} +{{- end }} + {{- with .Values.global.podAnnotations}} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.kubecostFrontend.tls }} + {{- if .Values.kubecostFrontend.tls.enabled }} + securityContext: + runAsUser: 0 + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + {{- else }} + securityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + {{- end }} + restartPolicy: Always + serviceAccountName: {{ template "cost-analyzer.serviceAccountName" . }} + volumes: + {{- if .Values.agent }} + - name: config-store + {{- if ((.Values.agentCsi).enabled) }} + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: "{{ .Values.agentCsi.secretProvider.name }}" + {{- else }} + secret: + secretName: {{ .Values.agentKeySecretName }} + {{- end }} + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.gcpSecretName }} + items: + - key: {{ .Values.kubecostProductConfigs.gcpSecretKeyName | default "compute-viewer-kubecost-key.json" }} + path: service-key.json + {{- end }} + {{- if .Values.kubecostProductConfigs.serviceKeySecretName }} + - name: service-key-secret + secret: + secretName: {{ .Values.kubecostProductConfigs.serviceKeySecretName }} + {{- else if .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + secret: + secretName: cloud-service-key + {{- end }} + {{- if .Values.kubecostProductConfigs.azureStorageSecretName }} + - name: azure-storage-config + secret: + secretName: {{ .Values.kubecostProductConfigs.azureStorageSecretName }} + items: + - key: azure-storage-config.json + path: azure-storage-config.json + {{- else if .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + secret: + secretName: azure-storage-config + {{- end }} + {{- if .Values.kubecostProductConfigs.cloudIntegrationSecret }} + - name: cloud-integration + secret: + secretName: {{ .Values.kubecostProductConfigs.cloudIntegrationSecret }} + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- else if or .Values.kubecostProductConfigs.cloudIntegrationJSON ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + secret: + secretName: cloud-integration + items: + - key: cloud-integration.json + path: cloud-integration.json + {{- end }} + {{- end }} + - name: persistent-configs +{{- if .Values.persistentVolume }} +{{- if .Values.persistentVolume.enabled }} + persistentVolumeClaim: +{{- if .Values.persistentVolume.existingClaim }} + claimName: {{ .Values.persistentVolume.existingClaim }} +{{- else }} + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end -}} +{{- else }} + emptyDir: {} +{{- end -}} +{{- else }} + persistentVolumeClaim: + claimName: {{ template "cost-analyzer.fullname" . }} +{{- end }} + initContainers: +{{- if .Values.supportNFS }} + - name: config-db-perms-fix + {{- if .Values.initChownDataImage }} + image: {{ .Values.initChownDataImage }} + {{- else }} + image: busybox + {{- end }} + resources: +{{ toYaml .Values.initChownData.resources | indent 12 }} + command: ["sh", "-c", "/bin/chmod -R 777 /var/configs"] + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + securityContext: + runAsUser: 0 +{{- end }} + containers: + {{- if .Values.kubecostModel }} + {{- if .Values.kubecostModel.fullImageName }} + - image: {{ .Values.kubecostModel.fullImageName }} + {{- else if .Values.imageVersion }} + - image: {{ .Values.kubecostModel.image }}:{{ .Values.imageVersion }} + {{- else if eq "development" .Chart.AppVersion }} + - image: gcr.io/kubecost1/cost-model-nightly:latest + {{- else }} + - image: {{ .Values.kubecostModel.image }}:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- else }} + - image: gcr.io/kubecost1/cost-model:prod-{{ $.Chart.AppVersion }} + {{ end }} + {{- if .Values.kubecostModel.imagePullPolicy }} + imagePullPolicy: {{ .Values.kubecostModel.imagePullPolicy }} + {{- else }} + imagePullPolicy: Always + {{- end }} + name: {{ template "kubecost.kubeMetricsName" . }} + ports: + - name: tcp-metrics + protocol: TCP + containerPort: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + resources: +{{ toYaml .Values.kubecostMetrics.exporter.resources | indent 12 }} + readinessProbe: + httpGet: + path: /healthz + port: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 200 + volumeMounts: + - name: persistent-configs + mountPath: /var/configs + {{- if .Values.agent }} + - name: config-store + mountPath: /var/secrets + {{- end }} + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: gcp-key-secret + mountPath: /var/secrets + {{- end }} + {{- if or .Values.kubecostProductConfigs.azureStorageSecretName .Values.kubecostProductConfigs.azureStorageCreateSecret }} + - name: azure-storage-config + mountPath: /var/azure-storage-config + {{- end }} + {{- if or (.Values.kubecostProductConfigs.cloudIntegrationSecret) (.Values.kubecostProductConfigs.cloudIntegrationJSON) ((.Values.kubecostProductConfigs).athenaBucketName) }} + - name: cloud-integration + mountPath: /var/configs/cloud-integration + {{- end }} + {{- if or .Values.kubecostProductConfigs.serviceKeySecretName .Values.kubecostProductConfigs.createServiceKeySecret }} + - name: service-key-secret + mountPath: /var/secrets + {{- end }} + {{- end }} + args: + {{- if .Values.cloudAgent }} + - cloud-agent + {{- else }} + - agent + {{- end }} + {{- if .Values.kubecostMetrics.exporter.extraArgs }} + {{ toYaml .Values.kubecostMetrics.exporter.extraArgs | nindent 12 }} + {{- end }} + env: + - name: PROMETHEUS_SERVER_ENDPOINT + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: prometheus-server-endpoint + {{- if .Values.cloudAgent }} + - name: CLOUD_AGENT_KEY + value: {{ .Values.cloudAgentKey }} + - name: CLOUD_REPORTING_SERVER + value: {{ .Values.cloudReportingServer }} + {{- end }} + - name: CLOUD_PROVIDER_API_KEY + value: "AIzaSyDXQPG_MHUEy9neR7stolq6l0ujXmjJlvk" # The GCP Pricing API requires a key. + {{- if .Values.kubecostProductConfigs }} + {{- if .Values.kubecostProductConfigs.gcpSecretName }} + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /var/configs/key.json + {{- end }} + {{- end }} + - name: CONFIG_PATH + value: /var/configs/ + - name: KUBECOST_METRICS_PORT + value: {{ (quote .Values.kubecostMetrics.exporter.port) | default (quote 9005) }} + {{- if .Values.agent }} + - name: KUBECOST_CONFIG_BUCKET + value: /var/secrets/object-store.yaml + - name: EXPORT_CLUSTER_INFO_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.exportClusterInfo) | default (quote true) }} + - name: EXPORT_CLUSTER_CACHE_ENABLED + value: {{ (quote .Values.kubecostMetrics.exporter.exportClusterCache) | default (quote true) }} + {{- end }} + - name: EMIT_POD_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitPodAnnotations) | default (quote false) }} + - name: EMIT_NAMESPACE_ANNOTATIONS_METRIC + value: {{ (quote .Values.kubecostMetrics.emitNamespaceAnnotations) | default (quote false) }} + - name: EMIT_KSM_V1_METRICS + value: {{ (quote .Values.kubecostMetrics.emitKsmV1Metrics) | default (quote true) }} + - name: EMIT_KSM_V1_METRICS_ONLY # ONLY emit KSM v1 metrics that do not exist in KSM 2 by default + value: {{ (quote .Values.kubecostMetrics.emitKsmV1MetricsOnly) | default (quote false) }} + - name: MAX_QUERY_CONCURRENCY + value: {{ (quote .Values.kubecostModel.maxQueryConcurrency) | default (quote 5) }} + {{- if .Values.global.prometheus.queryServiceBasicAuthSecretName}} + - name: DB_BASIC_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: USERNAME + - name: DB_BASIC_AUTH_PW + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBasicAuthSecretName }} + key: PASSWORD + {{- end }} + {{- if .Values.global.prometheus.queryServiceBearerTokenSecretName }} + - name: DB_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: {{ .Values.global.prometheus.queryServiceBearerTokenSecretName }} + key: TOKEN + {{- end }} + {{- if .Values.global.prometheus.insecureSkipVerify }} + - name: INSECURE_SKIP_VERIFY + value: {{ (quote .Values.global.prometheus.insecureSkipVerify) }} + {{- end }} + {{- if .Values.cloudAgentClusterId }} + - name: CLUSTER_ID + value: {{ .Values.cloudAgentClusterId }} + {{- else if and (.Values.prometheus.server.global.external_labels.cluster_id) (not .Values.prometheus.server.clusterIDConfigmap) }} + - name: CLUSTER_ID + value: {{ .Values.prometheus.server.global.external_labels.cluster_id }} + {{- end }} + {{- if .Values.prometheus.server.clusterIDConfigmap }} + - name: CLUSTER_ID + valueFrom: + configMapKeyRef: + name: {{ .Values.prometheus.server.clusterIDConfigmap }} + key: CLUSTER_ID + {{- end }} + {{- if .Values.kubecostModel.promClusterIDLabel }} + - name: PROM_CLUSTER_ID_LABEL + value: {{ .Values.kubecostModel.promClusterIDLabel }} + {{- end }} + - name: PV_ENABLED + value: {{ (quote .Values.persistentVolume.enabled) | default (quote true) }} + - name: RELEASE_NAME + value: {{ .Release.Name }} + - name: KUBECOST_NAMESPACE + value: {{ .Release.Namespace }} + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBECOST_TOKEN + valueFrom: + configMapKeyRef: + name: {{ template "cost-analyzer.fullname" . }} + key: kubecost-token + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.kubecostMetrics.exporter.priorityClassName }} + priorityClassName: {{ .Values.kubecostMetrics.exporter.priorityClassName }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-monitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-monitor-template.yaml new file mode 100644 index 0000000000..f858b77a36 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-monitor-template.yaml @@ -0,0 +1,41 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if .Values.kubecostMetrics.exporter.enabled }} +{{- if .Values.kubecostMetrics.exporter.serviceMonitor }} +{{- if .Values.kubecostMetrics.exporter.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.kubecostMetrics.exporter.serviceMonitor.additionalLabels }} + {{ toYaml .Values.kubecostMetrics.exporter.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: tcp-metrics + honorLabels: true + interval: 1m + scrapeTimeout: 10s + path: /metrics + scheme: http + {{- with .Values.kubecostMetrics.exporter.serviceMonitor.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.kubecostMetrics.exporter.serviceMonitor.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ include "kubecost.kubeMetricsName" . }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-template.yaml new file mode 100644 index 0000000000..80ef198f87 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-metrics-service-template.yaml @@ -0,0 +1,34 @@ +{{- if .Values.kubecostMetrics }} +{{- if .Values.kubecostMetrics.exporter }} +{{- if .Values.kubecostMetrics.exporter.enabled }} +{{- $prometheusScrape := ternary .Values.kubecostMetrics.exporter.prometheusScrape true (kindIs "bool" .Values.kubecostMetrics.exporter.prometheusScrape) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kubecost.kubeMetricsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ unset (include "cost-analyzer.commonLabels" . | fromYaml) "app" | toYaml | nindent 4 }} + app: {{ template "kubecost.kubeMetricsName" . }} +{{- if (or .Values.kubecostMetrics.exporter.service.annotations $prometheusScrape) }} + annotations: +{{- if .Values.kubecostMetrics.exporter.service.annotations }} +{{ toYaml .Values.kubecostMetrics.exporter.service.annotations | indent 4 }} +{{- end }} +{{- if $prometheusScrape }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ (quote .Values.kubecostMetrics.exporter.port) | default (quote 9005) }} +{{- end }} +{{- end }} +spec: + ports: + - name: tcp-metrics + port: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + protocol: TCP + targetPort: {{ .Values.kubecostMetrics.exporter.port | default 9005 }} + selector: + app: {{ template "kubecost.kubeMetricsName" . }} + type: ClusterIP +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-oidc-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-oidc-secret-template.yaml new file mode 100644 index 0000000000..3815145129 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-oidc-secret-template.yaml @@ -0,0 +1,16 @@ +{{- if .Values.oidc }} +{{- if and (not .Values.oidc.existingCustomSecret.enabled) .Values.oidc.secretName }} +{{- if .Values.oidc.clientSecret }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.oidc.secretName }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +stringData: + clientSecret: {{ .Values.oidc.clientSecret }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-priority-class-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-priority-class-template.yaml new file mode 100644 index 0000000000..7a176d72ad --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-priority-class-template.yaml @@ -0,0 +1,15 @@ +{{- if .Values.priority }} +{{- if .Values.priority.enabled }} +{{- if eq (len .Values.priority.name) 0 }} +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: {{ template "cost-analyzer.fullname" . }}-priority + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +value: {{ .Values.priority.value | default "1000000" }} +globalDefault: false +description: "Priority class for scheduling the cost-analyzer pod" +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-saml-secret-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-saml-secret-template.yaml new file mode 100644 index 0000000000..e9a323057d --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/kubecost-saml-secret-template.yaml @@ -0,0 +1,12 @@ +{{- if .Values.saml.enabled }} +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ .Values.saml.authSecretName | default "kubecost-saml-secret" }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +stringData: + clientSecret: {{ .Values.saml.authSecret | default (randAlphaNum 32 | quote) }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-configmap-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-configmap-template.yaml new file mode 100644 index 0000000000..08b93ee84e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-configmap-template.yaml @@ -0,0 +1,21 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} +data: + default.conf: | + server { + listen {{ .Values.global.mimirProxy.port }}; + location / { + proxy_pass {{ .Values.global.mimirProxy.mimirEndpoint }}; + proxy_set_header X-Scope-OrgID "{{ .Values.global.mimirProxy.orgIdentifier }}"; + {{- if .Values.global.mimirProxy.basicAuth }} + proxy_set_header Authorization "Basic {{ (printf "%s:%s" .Values.global.mimirProxy.basicAuth.username .Values.global.mimirProxy.basicAuth.password) | b64enc }}"; + {{- end }} + } + } +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-deployment-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-deployment-template.yaml new file mode 100644 index 0000000000..cbe8519b48 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-deployment-template.yaml @@ -0,0 +1,46 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} + labels: + app: mimir-proxy + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: 1 + selector: + matchLabels: + app: mimir-proxy + template: + metadata: + labels: + app: mimir-proxy + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + containers: + - name: {{ .Values.global.mimirProxy.name }} + image: {{ .Values.global.mimirProxy.image }} + ports: + - containerPort: {{ .Values.global.mimirProxy.port }} + protocol: TCP + resources: {} + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /etc/nginx/conf.d + name: default-conf + readOnly: true + volumes: + - name: default-conf + configMap: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + items: + - key: default.conf + path: default.conf +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-service-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-service-template.yaml new file mode 100644 index 0000000000..5e46b62f99 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/mimir-proxy-service-template.yaml @@ -0,0 +1,18 @@ +{{- if .Values.global.mimirProxy }} +{{- if .Values.global.mimirProxy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "cost-analyzer.fullname" . }}-mimir-proxy + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: mimir-proxy + protocol: TCP + port: {{ .Values.global.mimirProxy.port }} + targetPort: {{ .Values.global.mimirProxy.port }} + selector: + app: mimir-proxy + type: ClusterIP +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/model-ingress-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/model-ingress-template.yaml new file mode 100644 index 0000000000..b55b2986c5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/model-ingress-template.yaml @@ -0,0 +1,51 @@ +{{- if .Values.kubecostModel.ingress -}} +{{- if .Values.kubecostModel.ingress.enabled -}} +{{- $fullName := include "cost-analyzer.fullname" . -}} +{{- $serviceName := include "cost-analyzer.serviceName" . -}} +{{- $ingressPaths := .Values.kubecostModel.ingress.paths -}} +{{- $ingressPathType := .Values.kubecostModel.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }}-model + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- with .Values.kubecostModel.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.kubecostModel.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if .Values.kubecostModel.ingress.className }} + ingressClassName: {{ .Values.kubecostModel.ingress.className }} +{{- end }} +{{- if .Values.kubecostModel.ingress.tls }} + tls: + {{- range .Values.kubecostModel.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.kubecostModel.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + {{- range $ingressPaths }} + - path: {{ . }} + pathType: {{ $ingressPathType }} + backend: + service: + name: {{ $serviceName }} + port: + name: tcp-model + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/network-costs-servicemonitor-template.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/network-costs-servicemonitor-template.yaml new file mode 100644 index 0000000000..3cef9547d8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/network-costs-servicemonitor-template.yaml @@ -0,0 +1,32 @@ +{{- if .Values.serviceMonitor.networkCosts.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cost-analyzer.networkCostsName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} + {{- if .Values.serviceMonitor.networkCosts.additionalLabels }} + {{ toYaml .Values.serviceMonitor.networkCosts.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: metrics + honorLabels: true + interval: {{ .Values.serviceMonitor.networkCosts.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.networkCosts.scrapeTimeout }} + path: /metrics + scheme: http + {{- with .Values.serviceMonitor.networkCosts.metricRelabelings }} + metricRelabelings: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.serviceMonitor.networkCosts.relabelings }} + relabelings: {{ toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app: {{ include "cost-analyzer.networkCostsName" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/plugins-config.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/plugins-config.yaml new file mode 100644 index 0000000000..bd939ac1e8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/plugins-config.yaml @@ -0,0 +1,13 @@ +{{- if and (not .Values.kubecostModel.plugins.existingCustomSecret.enabled) .Values.kubecostModel.plugins.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.kubecostModel.plugins.secretName }} + labels: + {{ include "cost-analyzer.commonLabels" . | nindent 4 }} +data: + {{- range $key, $config := .Values.kubecostModel.plugins.configs }} + {{ $key }}_config.json: + {{ $config | b64enc | indent 4}} + {{- end }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-configmap.yaml new file mode 100644 index 0000000000..8f5b8315f3 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-configmap.yaml @@ -0,0 +1,21 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled (and (empty .Values.prometheus.alertmanager.configMapOverrideName) (empty .Values.prometheus.alertmanager.configFromSecret)) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +data: +{{- $root := . -}} +{{- range $key, $value := .Values.prometheus.alertmanagerFiles }} + {{- if $key | regexMatch ".*\\.ya?ml$" }} + {{ $key }}: | +{{ toYaml $value | default "{}" | indent 4 }} + {{- else }} + {{ $key }}: {{ toYaml $value | indent 4 }} + {{- end }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-deployment.yaml new file mode 100644 index 0000000000..b3af15532a --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-deployment.yaml @@ -0,0 +1,148 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled (not .Values.prometheus.alertmanager.statefulSet.enabled) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.alertmanager.replicaCount }} + {{- if .Values.prometheus.alertmanager.strategy }} + strategy: +{{ toYaml .Values.prometheus.alertmanager.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.alertmanager.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.podLabels}} + {{ toYaml .Values.prometheus.alertmanager.podLabels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.alertmanager.schedulerName }} + schedulerName: "{{ .Values.prometheus.alertmanager.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.alertmanager" . }} +{{- if .Values.prometheus.alertmanager.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.alertmanager.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }} + image: "{{ .Values.prometheus.alertmanager.image.repository }}:{{ .Values.prometheus.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.alertmanager.image.pullPolicy }}" + env: + {{- range $key, $value := .Values.prometheus.alertmanager.extraEnv }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + args: + - --config.file=/etc/config/{{ .Values.prometheus.alertmanager.configFileName }} + - --storage.path={{ .Values.prometheus.alertmanager.persistentVolume.mountPath }} + - --cluster.advertise-address=$(POD_IP):6783 + {{- range $key, $value := .Values.prometheus.alertmanager.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.alertmanager.baseURL }} + - --web.external-url={{ .Values.prometheus.alertmanager.baseURL }} + {{- end }} + + ports: + - containerPort: 9093 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.alertmanager.prefixURL }}/-/ready + port: 9093 + initialDelaySeconds: 30 + timeoutSeconds: 30 + resources: +{{ toYaml .Values.prometheus.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: "{{ .Values.prometheus.alertmanager.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.alertmanager.persistentVolume.subPath }}" + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + + {{- if .Values.prometheus.configmapReload.alertmanager.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }}-{{ .Values.prometheus.configmapReload.alertmanager.name }} + image: "{{ .Values.prometheus.configmapReload.alertmanager.image.repository }}:{{ .Values.prometheus.configmapReload.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.alertmanager.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9093{{ .Values.prometheus.alertmanager.prefixURL }}/-/reload + resources: +{{ toYaml .Values.prometheus.configmapReload.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.alertmanager.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.alertmanager.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.alertmanager.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.affinity }} + affinity: +{{ toYaml .Values.prometheus.alertmanager.affinity | indent 8 }} + {{- end }} + volumes: + - name: config-volume + {{- if empty .Values.prometheus.alertmanager.configFromSecret }} + configMap: + name: {{ if .Values.prometheus.alertmanager.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.alertmanager.configMapOverrideName }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- else }} + secret: + secretName: {{ .Values.prometheus.alertmanager.configFromSecret }} + {{- end }} + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + - name: storage-volume + {{- if .Values.prometheus.alertmanager.persistentVolume.enabled }} + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.alertmanager.persistentVolume.existingClaim }}{{ .Values.prometheus.alertmanager.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- else }} + emptyDir: {} + {{- end -}} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-ingress.yaml new file mode 100644 index 0000000000..41757e0e13 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-ingress.yaml @@ -0,0 +1,41 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.ingress.enabled -}} +{{- $releaseName := .Release.Name -}} +{{- $serviceName := include "prometheus.alertmanager.fullname" . }} +{{- $servicePort := .Values.prometheus.alertmanager.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.alertmanager.ingress.extraPaths -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.alertmanager.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.ingress.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- range $key, $value := .Values.prometheus.alertmanager.ingress.extraLabels }} + {{ $key }}: {{ $value }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + rules: + {{- range .Values.prometheus.alertmanager.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.alertmanager.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.alertmanager.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-networkpolicy.yaml new file mode 100644 index 0000000000..c24a76ae70 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-networkpolicy.yaml @@ -0,0 +1,22 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + ingress: + - from: + - podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 12 }} + - ports: + - port: 9093 +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pdb.yaml new file mode 100644 index 0000000000..123d24ee01 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pdb.yaml @@ -0,0 +1,16 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.alertmanager.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.alertmanager.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.alertmanager.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pvc.yaml new file mode 100644 index 0000000000..dea65e5e5e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-pvc.yaml @@ -0,0 +1,35 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if not .Values.prometheus.alertmanager.statefulSet.enabled -}} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.persistentVolume.enabled -}} +{{- if not .Values.prometheus.alertmanager.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.alertmanager.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.alertmanager.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.alertmanager.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.alertmanager.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.alertmanager.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.alertmanager.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.alertmanager.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service-headless.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service-headless.yaml new file mode 100644 index 0000000000..2f68f41260 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service-headless.yaml @@ -0,0 +1,33 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.statefulSet.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.alertmanager.statefulSet.headless.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.statefulSet.headless.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.statefulSet.headless.labels }} +{{ toYaml .Values.prometheus.alertmanager.statefulSet.headless.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }}-headless + namespace: {{ .Release.Namespace }} +spec: + clusterIP: None + ports: + - name: http + port: {{ .Values.prometheus.alertmanager.statefulSet.headless.servicePort }} + protocol: TCP + targetPort: 9093 +{{- if .Values.prometheus.alertmanager.statefulSet.headless.enableMeshPeer }} + - name: meshpeer + port: 6783 + protocol: TCP + targetPort: 6783 +{{- end }} + selector: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 4 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service.yaml new file mode 100644 index 0000000000..838d39ba44 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-service.yaml @@ -0,0 +1,55 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.alertmanager.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.alertmanager.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.service.labels }} +{{ toYaml .Values.prometheus.alertmanager.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.alertmanager.service.clusterIP }} + clusterIP: {{ .Values.prometheus.alertmanager.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.alertmanager.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.alertmanager.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.alertmanager.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.alertmanager.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.alertmanager.service.servicePort }} + protocol: TCP + targetPort: 9093 + {{- if .Values.prometheus.alertmanager.service.nodePort }} + nodePort: {{ .Values.prometheus.alertmanager.service.nodePort }} + {{- end }} +{{- if .Values.prometheus.alertmanager.service.enableMeshPeer }} + - name: meshpeer + port: 6783 + protocol: TCP + targetPort: 6783 +{{- end }} + selector: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 4 }} +{{- if .Values.prometheus.alertmanager.service.sessionAffinity }} + sessionAffinity: {{ .Values.prometheus.alertmanager.service.sessionAffinity }} +{{- end }} + type: "{{ .Values.prometheus.alertmanager.service.type }}" +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-serviceaccount.yaml new file mode 100644 index 0000000000..99257bbf88 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.serviceAccounts.alertmanager.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.alertmanager" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-statefulset.yaml new file mode 100644 index 0000000000..26e05f1fb4 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-alertmanager-statefulset.yaml @@ -0,0 +1,155 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.alertmanager.enabled .Values.prometheus.alertmanager.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 4 }} + name: {{ template "prometheus.alertmanager.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "prometheus.alertmanager.fullname" . }}-headless + selector: + matchLabels: + {{- include "prometheus.alertmanager.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.alertmanager.replicaCount }} + podManagementPolicy: {{ .Values.prometheus.alertmanager.statefulSet.podManagementPolicy }} + template: + metadata: + {{- if .Values.prometheus.alertmanager.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.alertmanager.labels" . | nindent 8 }} + spec: +{{- if .Values.prometheus.alertmanager.affinity }} + affinity: +{{ toYaml .Values.prometheus.alertmanager.affinity | indent 8 }} +{{- end }} +{{- if .Values.prometheus.alertmanager.schedulerName }} + schedulerName: "{{ .Values.prometheus.alertmanager.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.alertmanager" . }} +{{- if .Values.prometheus.alertmanager.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.alertmanager.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }} + image: "{{ .Values.prometheus.alertmanager.image.repository }}:{{ .Values.prometheus.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.alertmanager.image.pullPolicy }}" + env: + {{- range $key, $value := .Values.prometheus.alertmanager.extraEnv }} + - name: {{ $key }} + value: {{ $value }} + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + args: + - --config.file=/etc/config/alertmanager.yml + - --storage.path={{ .Values.prometheus.alertmanager.persistentVolume.mountPath }} + - --cluster.advertise-address=$(POD_IP):6783 + {{- if .Values.prometheus.alertmanager.statefulSet.headless.enableMeshPeer }} + - --cluster.listen-address=0.0.0.0:6783 + {{- range $n := until (.Values.prometheus.alertmanager.replicaCount | int) }} + - --cluster.peer={{ template "prometheus.alertmanager.fullname" $ }}-{{ $n }}.{{ template "prometheus.alertmanager.fullname" $ }}-headless:6783 + {{- end }} + {{- end }} + {{- range $key, $value := .Values.prometheus.alertmanager.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.alertmanager.baseURL }} + - --web.external-url={{ .Values.prometheus.alertmanager.baseURL }} + {{- end }} + + ports: + - containerPort: 9093 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.alertmanager.prefixURL }}/#/status + port: 9093 + initialDelaySeconds: 30 + timeoutSeconds: 30 + resources: +{{ toYaml .Values.prometheus.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: "{{ .Values.prometheus.alertmanager.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.alertmanager.persistentVolume.subPath }}" + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.configmapReload.alertmanager.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.alertmanager.name }}-{{ .Values.prometheus.configmapReload.alertmanager.name }} + image: "{{ .Values.prometheus.configmapReload.alertmanager.image.repository }}:{{ .Values.prometheus.configmapReload.alertmanager.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.alertmanager.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://localhost:9093{{ .Values.prometheus.alertmanager.prefixURL }}/-/reload + resources: +{{ toYaml .Values.prometheus.configmapReload.alertmanager.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.alertmanager.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.alertmanager.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.alertmanager.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.alertmanager.tolerations | indent 8 }} + {{- end }} + volumes: + - name: config-volume + configMap: + name: {{ if .Values.prometheus.alertmanager.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.alertmanager.configMapOverrideName }}{{- else }}{{ template "prometheus.alertmanager.fullname" . }}{{- end }} + {{- range .Values.prometheus.alertmanager.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} +{{- if .Values.prometheus.alertmanager.persistentVolume.enabled }} + volumeClaimTemplates: + - metadata: + name: storage-volume + {{- if .Values.prometheus.alertmanager.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.annotations | indent 10 }} + {{- end }} + spec: + accessModes: +{{ toYaml .Values.prometheus.alertmanager.persistentVolume.accessModes | indent 10 }} + resources: + requests: + storage: "{{ .Values.prometheus.alertmanager.persistentVolume.size }}" + {{- if .Values.prometheus.server.persistentVolume.storageClass }} + {{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.prometheus.alertmanager.persistentVolume.storageClass }}" + {{- end }} + {{- end }} +{{- else }} + - name: storage-volume + emptyDir: {} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-daemonset.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-daemonset.yaml new file mode 100644 index 0000000000..3529d6bdd8 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-daemonset.yaml @@ -0,0 +1,139 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.nodeExporter.enabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: +{{- if .Values.prometheus.nodeExporter.deploymentAnnotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.deploymentAnnotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.nodeExporter.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.nodeExporter.matchLabels" . | nindent 6 }} + {{- if .Values.prometheus.nodeExporter.updateStrategy }} + updateStrategy: +{{ toYaml .Values.prometheus.nodeExporter.updateStrategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.nodeExporter.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.prometheus.nodeExporter.pod.labels }} +{{ toYaml .Values.prometheus.nodeExporter.pod.labels | indent 8 }} +{{- end }} + spec: +{{- if .Values.prometheus.nodeExporter.affinity }} + affinity: +{{ toYaml .Values.prometheus.nodeExporter.affinity | indent 8 }} +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.nodeExporter" . }} +{{- if .Values.prometheus.nodeExporter.dnsPolicy }} + dnsPolicy: "{{ .Values.prometheus.nodeExporter.dnsPolicy }}" +{{- end }} +{{- if .Values.prometheus.nodeExporter.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.nodeExporter.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.nodeExporter.name }} + image: "{{ .Values.prometheus.nodeExporter.image.repository }}:{{ .Values.prometheus.nodeExporter.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.nodeExporter.image.pullPolicy }}" + args: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + - --web.listen-address=:{{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- end }} + {{- range $key, $value := .Values.prometheus.nodeExporter.extraArgs }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + ports: + - name: metrics + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + containerPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- else }} + containerPort: 9100 + {{- end }} + hostPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + resources: +{{ toYaml .Values.prometheus.nodeExporter.resources | indent 12 }} + volumeMounts: + - name: proc + mountPath: /host/proc + readOnly: true + - name: sys + mountPath: /host/sys + readOnly: true + {{- range .Values.prometheus.nodeExporter.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- if .mountPropagation }} + mountPropagation: {{ .mountPropagation }} + {{- end }} + {{- end }} + {{- range .Values.prometheus.nodeExporter.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + hostNetwork: true + {{- end }} + {{- if .Values.prometheus.nodeExporter.hostPID }} + hostPID: true + {{- end }} + {{- if .Values.prometheus.nodeExporter.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.nodeExporter.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.nodeExporter.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.nodeExporter.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.nodeExporter.securityContext | indent 8 }} + {{- end }} + volumes: + - name: proc + hostPath: + path: /proc + - name: sys + hostPath: + path: /sys + {{- range .Values.prometheus.nodeExporter.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.nodeExporter.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-ocp-scc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-ocp-scc.yaml new file mode 100644 index 0000000000..e226f9beac --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-ocp-scc.yaml @@ -0,0 +1,29 @@ +{{- if and (.Capabilities.APIVersions.Has "security.openshift.io/v1/SecurityContextConstraints") (.Values.global.platforms.openshift.scc.nodeExporter) (.Values.prometheus.nodeExporter.enabled) }} +apiVersion: security.openshift.io/v1 +kind: SecurityContextConstraints +metadata: + name: {{ template "prometheus.nodeExporter.fullname" . }} +priority: 10 +allowPrivilegedContainer: true +allowHostDirVolumePlugin: true +allowHostNetwork: true +allowHostPorts: true +allowHostPID: true +allowHostIPC: false +readOnlyRootFilesystem: false +runAsUser: + type: RunAsAny +fsGroup: + type: RunAsAny +seLinuxContext: + type: RunAsAny +supplementalGroups: + type: RunAsAny +seccompProfiles: +- runtime/default +volumes: + - hostPath + - projected +users: + - system:serviceaccount:{{ .Release.Namespace }}:{{ template "prometheus.serviceAccountName.nodeExporter" . }} +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-service.yaml new file mode 100644 index 0000000000..9b8167e8d7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-service.yaml @@ -0,0 +1,47 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.nodeExporter.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.nodeExporter.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.nodeExporter.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} +{{- if .Values.prometheus.nodeExporter.service.labels }} +{{ toYaml .Values.prometheus.nodeExporter.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.nodeExporter.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.nodeExporter.service.clusterIP }} + clusterIP: {{ .Values.prometheus.nodeExporter.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.nodeExporter.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.nodeExporter.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.nodeExporter.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.nodeExporter.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: tcp-metrics + port: {{ .Values.prometheus.nodeExporter.service.servicePort }} + protocol: TCP + {{- if .Values.prometheus.nodeExporter.hostNetwork }} + targetPort: {{ .Values.prometheus.nodeExporter.service.hostPort }} + {{- else }} + targetPort: 9100 + {{- end }} + selector: + {{- include "prometheus.nodeExporter.matchLabels" . | nindent 4 }} + type: "{{ .Values.prometheus.nodeExporter.service.type }}" +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-serviceaccount.yaml new file mode 100644 index 0000000000..3cb68d8e46 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-node-exporter-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.nodeExporter.enabled .Values.prometheus.serviceAccounts.nodeExporter.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.nodeExporter.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.nodeExporter" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-deployment.yaml new file mode 100644 index 0000000000..072c028d1e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-deployment.yaml @@ -0,0 +1,106 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + {{- if .Values.prometheus.pushgateway.schedulerName }} + schedulerName: "{{ .Values.prometheus.pushgateway.schedulerName }}" + {{- end }} + matchLabels: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.pushgateway.replicaCount }} + {{- if .Values.prometheus.pushgateway.strategy }} + strategy: +{{ toYaml .Values.prometheus.pushgateway.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.pushgateway.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "prometheus.serviceAccountName.pushgateway" . }} +{{- if .Values.prometheus.pushgateway.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.pushgateway.priorityClassName }}" +{{- end }} + containers: + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.pushgateway.name }} + image: "{{ .Values.prometheus.pushgateway.image.repository }}:{{ .Values.prometheus.pushgateway.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.pushgateway.image.pullPolicy }}" + args: + {{- range $key, $value := .Values.prometheus.pushgateway.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + ports: + - containerPort: 9091 + livenessProbe: + httpGet: + {{- if (index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix") }} + path: /{{ index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix" }}/-/healthy + {{- else }} + path: /-/healthy + {{- end }} + port: 9091 + initialDelaySeconds: 10 + timeoutSeconds: 10 + readinessProbe: + httpGet: + {{- if (index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix") }} + path: /{{ index .Values.prometheus "pushgateway" "extraArgs" "web.route-prefix" }}/-/ready + {{- else }} + path: /-/ready + {{- end }} + port: 9091 + initialDelaySeconds: 10 + timeoutSeconds: 10 + resources: +{{ toYaml .Values.prometheus.pushgateway.resources | indent 12 }} + {{- if .Values.prometheus.pushgateway.persistentVolume.enabled }} + volumeMounts: + - name: storage-volume + mountPath: "{{ .Values.prometheus.pushgateway.persistentVolume.mountPath }}" + subPath: "{{ .Values.prometheus.pushgateway.persistentVolume.subPath }}" + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.pushgateway.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.pushgateway.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.pushgateway.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.affinity }} + affinity: +{{ toYaml .Values.prometheus.pushgateway.affinity | indent 8 }} + {{- end }} + {{- if .Values.prometheus.pushgateway.persistentVolume.enabled }} + volumes: + - name: storage-volume + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.pushgateway.persistentVolume.existingClaim }}{{ .Values.prometheus.pushgateway.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.pushgateway.fullname" . }}{{- end }} + {{- end -}} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-ingress.yaml new file mode 100644 index 0000000000..2d3f1d2836 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-ingress.yaml @@ -0,0 +1,38 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.prometheus.pushgateway.ingress.enabled -}} +{{- $releaseName := .Release.Name -}} +{{- $serviceName := include "prometheus.pushgateway.fullname" . }} +{{- $servicePort := .Values.prometheus.pushgateway.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.pushgateway.ingress.extraPaths -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.pushgateway.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.ingress.annotations | indent 4}} +{{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + rules: + {{- range .Values.prometheus.pushgateway.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.pushgateway.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.pushgateway.ingress.tls | indent 4 }} + {{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-networkpolicy.yaml new file mode 100644 index 0000000000..b6e41eedfc --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-networkpolicy.yaml @@ -0,0 +1,22 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.networkPolicy.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 6 }} + ingress: + - from: + - podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 12 }} + - ports: + - port: 9091 +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pdb.yaml new file mode 100644 index 0000000000..00f7e45024 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pdb.yaml @@ -0,0 +1,15 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.pushgateway.fullname" . }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.pushgateway.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.pushgateway.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pvc.yaml new file mode 100644 index 0000000000..ba22f5921c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-pvc.yaml @@ -0,0 +1,35 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +{{- if .Values.prometheus.pushgateway.persistentVolume.enabled -}} +{{- if not .Values.prometheus.pushgateway.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.pushgateway.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.pushgateway.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.pushgateway.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.pushgateway.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.pushgateway.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.pushgateway.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.pushgateway.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.pushgateway.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{ end }} +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-service.yaml new file mode 100644 index 0000000000..3e88117042 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-service.yaml @@ -0,0 +1,43 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.pushgateway.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.pushgateway.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.pushgateway.service.annotations | indent 4}} +{{- end }} + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} +{{- if .Values.prometheus.pushgateway.service.labels }} +{{ toYaml .Values.prometheus.pushgateway.service.labels | indent 4}} +{{- end }} + name: {{ template "prometheus.pushgateway.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.pushgateway.service.clusterIP }} + clusterIP: {{ .Values.prometheus.pushgateway.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.pushgateway.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.pushgateway.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.pushgateway.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.pushgateway.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.pushgateway.service.servicePort }} + protocol: TCP + targetPort: 9091 + selector: + {{- include "prometheus.pushgateway.matchLabels" . | nindent 4 }} + type: "{{ .Values.prometheus.pushgateway.service.type }}" +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-serviceaccount.yaml new file mode 100644 index 0000000000..1339e4b6bb --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-pushgateway-serviceaccount.yaml @@ -0,0 +1,11 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.pushgateway.enabled .Values.prometheus.serviceAccounts.pushgateway.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.pushgateway.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.pushgateway" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrole.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrole.yaml new file mode 100644 index 0000000000..3672195556 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrole.yaml @@ -0,0 +1,39 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.server.enabled .Values.prometheus.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} +rules: + - apiGroups: + - "" + resources: + - nodes + - nodes/proxy + - nodes/metrics + - services + - endpoints + - pods + - ingresses + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + - ingresses + verbs: + - get + - list + - watch + - nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrolebinding.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrolebinding.yaml new file mode 100644 index 0000000000..e03d8e443f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-clusterrolebinding.yaml @@ -0,0 +1,18 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if and .Values.prometheus.server.enabled .Values.prometheus.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "prometheus.serviceAccountName.server" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "prometheus.server.fullname" . }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-configmap.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-configmap.yaml new file mode 100644 index 0000000000..ca91b2d4a6 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-configmap.yaml @@ -0,0 +1,90 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if (empty .Values.prometheus.server.configMapOverrideName) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +data: +{{- $root := . -}} +{{- range $key, $value := .Values.prometheus.serverFiles }} + {{ $key }}: | +{{- if eq $key "prometheus.yml" }} + global: +{{ $root.Values.prometheus.server.global | toYaml | trimSuffix "\n" | indent 6 }} +{{- if $root.Values.global.amp.enabled }} + remote_write: + - url: {{ $root.Values.global.amp.remoteWriteService }} + sigv4: +{{ $root.Values.global.amp.sigv4 | toYaml | indent 8 }} +{{- end }} +{{- if $root.Values.prometheus.server.remoteWrite }} + remote_write: +{{ $root.Values.prometheus.server.remoteWrite | toYaml | indent 4 }} +{{- end }} +{{- if $root.Values.prometheus.server.remoteRead }} + remote_read: +{{ $root.Values.prometheus.server.remoteRead | toYaml | indent 4 }} +{{- end }} +{{- end }} +{{- if eq $key "alerts" }} +{{- if and (not (empty $value)) (empty $value.groups) }} + groups: +{{- range $ruleKey, $ruleValue := $value }} + - name: {{ $ruleKey -}}.rules + rules: +{{ $ruleValue | toYaml | trimSuffix "\n" | indent 6 }} +{{- end }} +{{- else }} +{{ toYaml $value | indent 4 }} +{{- end }} +{{- else }} +{{ toYaml $value | default "{}" | indent 4 }} +{{- end }} +{{- if eq $key "prometheus.yml" -}} +{{- if $root.Values.prometheus.extraScrapeConfigs }} +{{ tpl $root.Values.prometheus.extraScrapeConfigs $root | indent 4 }} +{{- end -}} +{{- if or ($root.Values.prometheus.alertmanager.enabled) ($root.Values.prometheus.server.alertmanagers) }} + alerting: +{{- if $root.Values.prometheus.alertRelabelConfigs }} +{{ $root.Values.prometheus.alertRelabelConfigs | toYaml | trimSuffix "\n" | indent 6 }} +{{- end }} + alertmanagers: +{{- if $root.Values.prometheus.server.alertmanagers }} +{{ toYaml $root.Values.prometheus.server.alertmanagers | indent 8 }} +{{- else }} + - kubernetes_sd_configs: + - role: pod + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + {{- if $root.Values.prometheus.alertmanager.prefixURL }} + path_prefix: {{ $root.Values.prometheus.alertmanager.prefixURL }} + {{- end }} + relabel_configs: + - source_labels: [__meta_kubernetes_namespace] + regex: {{ $root.Release.Namespace }} + action: keep + - source_labels: [__meta_kubernetes_pod_label_app] + regex: {{ template "prometheus.name" $root }} + action: keep + - source_labels: [__meta_kubernetes_pod_label_component] + regex: alertmanager + action: keep + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_probe] + regex: {{ index $root.Values.prometheus.alertmanager.podAnnotations "prometheus.io/probe" | default ".*" }} + action: keep + - source_labels: [__meta_kubernetes_pod_container_port_number] + regex: + action: drop +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-deployment.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-deployment.yaml new file mode 100644 index 0000000000..8f2d60d3e7 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-deployment.yaml @@ -0,0 +1,265 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if not .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: +{{- if .Values.prometheus.server.deploymentAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.deploymentAnnotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.server.replicaCount }} + {{- if .Values.prometheus.server.strategy }} + strategy: +{{ toYaml .Values.prometheus.server.strategy | indent 4 }} + {{- end }} + template: + metadata: + {{- if .Values.prometheus.server.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.podAnnotations | indent 8 }} + {{- end }} + labels: + {{/* + Force pod restarts on upgrades to ensure the configmap is current + */}} + {{- if not .Values.global.platforms.cicd.enabled }} + helm-rollout-restarter: {{ randAlphaNum 5 | quote }} + {{- end }} + {{- include "prometheus.server.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.podLabels}} + {{ toYaml .Values.prometheus.server.podLabels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.server.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.server.priorityClassName }}" +{{- end }} +{{- if .Values.prometheus.server.schedulerName }} + schedulerName: "{{ .Values.prometheus.server.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.server" . }} + {{- if .Values.prometheus.server.extraInitContainers }} + initContainers: +{{ toYaml .Values.prometheus.server.extraInitContainers | indent 8 }} + {{- end }} + containers: + {{- if .Values.prometheus.configmapReload.prometheus.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.configmapReload.prometheus.name }} + image: "{{ .Values.prometheus.configmapReload.prometheus.image.repository }}:{{ .Values.prometheus.configmapReload.prometheus.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.prometheus.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9090{{ .Values.prometheus.server.prefixURL }}/-/reload + {{- range $key, $value := .Values.prometheus.configmapReload.prometheus.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraVolumeDirs }} + - --watched-dir={{ . }} + {{- end }} + resources: + {{- toYaml .Values.prometheus.configmapReload.prometheus.resources | nindent 12 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- else }} + {{- toYaml .Values.prometheus.configmapReload.prometheus.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.prometheus.selfsignedCertConfigMapName }} + - name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + mountPath: /etc/ssl/certs/my-cert.pem + subPath: my-cert.pem + readOnly: false + {{- end }} + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }} + image: "{{ .Values.prometheus.server.image.repository }}:{{ .Values.prometheus.server.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.server.image.pullPolicy }}" + {{- if .Values.prometheus.server.env }} + env: +{{ toYaml .Values.prometheus.server.env | indent 12}} + {{- end }} + args: + {{- if .Values.prometheus.server.retention }} + - --storage.tsdb.retention.time={{ .Values.prometheus.server.retention }} + {{- end }} + {{- if .Values.prometheus.server.retentionSize }} + - --storage.tsdb.retention.size={{ .Values.prometheus.server.retentionSize }} + {{- end }} + - --config.file={{ .Values.prometheus.server.configPath }} + - --storage.tsdb.path={{ .Values.prometheus.server.persistentVolume.mountPath }} + - --web.console.libraries=/etc/prometheus/console_libraries + - --web.console.templates=/etc/prometheus/consoles + {{- range .Values.prometheus.server.extraFlags }} + - --{{ . }} + {{- end }} + {{- if .Values.prometheus.server.baseURL }} + - --web.external-url={{ .Values.prometheus.server.baseURL }} + {{- end }} + + {{- range $key, $value := .Values.prometheus.server.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + ports: + - containerPort: 9090 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/ready + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.readinessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.readinessProbeTimeout }} + failureThreshold: {{ .Values.prometheus.server.readinessProbeFailureThreshold }} + successThreshold: {{ .Values.prometheus.server.readinessProbeSuccessThreshold }} + livenessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/healthy + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.livenessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.livenessProbeTimeout }} + failureThreshold: {{ .Values.prometheus.server.livenessProbeFailureThreshold }} + successThreshold: {{ .Values.prometheus.server.livenessProbeSuccessThreshold }} + resources: + {{- toYaml .Values.prometheus.server.resources | nindent 12 }} + securityContext: + {{- if .Values.global.containerSecurityContext }} + {{- toYaml .Values.global.containerSecurityContext | nindent 12 }} + {{- else }} + {{- toYaml .Values.prometheus.server.containerSecurityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: {{ .Values.prometheus.server.persistentVolume.mountPath }} + subPath: "{{ .Values.prometheus.server.persistentVolume.subPath }}" + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.server.extraVolumeMounts }} + {{ toYaml .Values.prometheus.server.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.prometheus.server.sidecarContainers }} + {{- toYaml .Values.prometheus.server.sidecarContainers | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 0 }} + {{- end }} + {{- if .Values.prometheus.server.nodeSelector }} + nodeSelector: + {{- toYaml .Values.prometheus.server.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.securityContext }} + securityContext: + {{- if not .Values.prometheus.server.securityContext.fsGroup }} + fsGroupChangePolicy: OnRootMismatch + fsGroup: 1001 + {{- end }} + {{- toYaml .Values.prometheus.server.securityContext | nindent 8 }} + {{- else if and (.Values.global.platforms.openshift.enabled) (.Values.global.platforms.openshift.securityContext) }} + securityContext: + {{- toYaml .Values.global.platforms.openshift.securityContext | nindent 8 }} + {{- else if .Values.global.securityContext }} + securityContext: + {{- toYaml .Values.global.securityContext | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.server.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.affinity }} + affinity: +{{ toYaml .Values.prometheus.server.affinity | indent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.prometheus.server.terminationGracePeriodSeconds }} + volumes: + {{- if .Values.prometheus.selfsignedCertConfigMapName }} + - name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + configMap: + name: {{ .Values.prometheus.selfsignedCertConfigMapName }} + {{- end }} + - name: config-volume + configMap: + name: {{ if .Values.prometheus.server.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.server.configMapOverrideName }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + - name: storage-volume + {{- if .Values.prometheus.server.persistentVolume.enabled }} + persistentVolumeClaim: + claimName: {{ if .Values.prometheus.server.persistentVolume.existingClaim }}{{ .Values.prometheus.server.persistentVolume.existingClaim }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + {{- else }} + emptyDir: + {{- if .Values.prometheus.server.emptyDir.sizeLimit }} + sizeLimit: {{ .Values.prometheus.server.emptyDir.sizeLimit }} + {{- else }} + {} + {{- end -}} + {{- end -}} +{{- if .Values.prometheus.server.extraVolumes }} +{{ toYaml .Values.prometheus.server.extraVolumes | indent 8}} +{{- end }} + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ tpl .secretName $ }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-ingress.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-ingress.yaml new file mode 100644 index 0000000000..18a7835fce --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-ingress.yaml @@ -0,0 +1,45 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.prometheus.server.ingress.enabled) }} +{{- $serviceName := include "prometheus.server.fullname" . }} +{{- $servicePort := .Values.prometheus.server.service.servicePort -}} +{{- $extraPaths := .Values.prometheus.server.ingress.extraPaths -}} +{{- $pathType := .Values.prometheus.server.ingress.pathType -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: +{{- if .Values.prometheus.server.ingress.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.ingress.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- range $key, $value := .Values.prometheus.server.ingress.extraLabels }} + {{ $key }}: {{ $value }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.server.ingress.className }} + ingressClassName: {{ .Values.prometheus.server.ingress.className }} +{{- end }} + rules: + {{- range .Values.prometheus.server.ingress.hosts }} + {{- $url := splitList "/" . }} + - host: {{ first $url }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + - path: /{{ rest $url | join "/" }} + pathType: {{ $pathType }} + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end -}} +{{- if .Values.prometheus.server.ingress.tls }} + tls: +{{ toYaml .Values.prometheus.server.ingress.tls | indent 4 }} + {{- end -}} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-networkpolicy.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-networkpolicy.yaml new file mode 100644 index 0000000000..23b04419ca --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-networkpolicy.yaml @@ -0,0 +1,16 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.networkPolicy.enabled) }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + ingress: + - ports: + - port: 9090 +{{- end }} \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pdb.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pdb.yaml new file mode 100644 index 0000000000..52ceeb2484 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pdb.yaml @@ -0,0 +1,15 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "prometheus.server.fullname" . }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +spec: + maxUnavailable: {{ .Values.prometheus.server.podDisruptionBudget.maxUnavailable }} + selector: + matchLabels: + {{- include "prometheus.server.labels" . | nindent 6 }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pvc.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pvc.yaml new file mode 100644 index 0000000000..301a33e1a1 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-pvc.yaml @@ -0,0 +1,37 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if not .Values.prometheus.server.statefulSet.enabled -}} +{{- if .Values.prometheus.server.persistentVolume.enabled -}} +{{- if not .Values.prometheus.server.persistentVolume.existingClaim -}} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + {{- if .Values.prometheus.server.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.persistentVolume.annotations | indent 4 }} + {{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + accessModes: +{{ toYaml .Values.prometheus.server.persistentVolume.accessModes | indent 4 }} +{{- if .Values.prometheus.server.persistentVolume.storageClass }} +{{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.prometheus.server.persistentVolume.storageClass }}" +{{- end }} +{{- end }} +{{- if .Values.prometheus.server.persistentVolume.volumeBindingMode }} + volumeBindingModeName: "{{ .Values.prometheus.server.persistentVolume.volumeBindingMode }}" +{{- end }} + resources: + requests: + storage: "{{ .Values.prometheus.server.persistentVolume.size }}" +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service-headless.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service-headless.yaml new file mode 100644 index 0000000000..019803d30c --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service-headless.yaml @@ -0,0 +1,29 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.server.statefulSet.headless.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.statefulSet.headless.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- if .Values.prometheus.server.statefulSet.headless.labels }} +{{ toYaml .Values.prometheus.server.statefulSet.headless.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }}-headless + namespace: {{ .Release.Namespace }} +spec: + clusterIP: None + ports: + - name: http + port: {{ .Values.prometheus.server.statefulSet.headless.servicePort }} + protocol: TCP + targetPort: 9090 + selector: + {{- include "prometheus.server.matchLabels" . | nindent 4 }} +{{- end -}} +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service.yaml new file mode 100644 index 0000000000..69f093c38e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-service.yaml @@ -0,0 +1,62 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.prometheus.server.service.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.service.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} +{{- if .Values.prometheus.server.service.labels }} +{{ toYaml .Values.prometheus.server.service.labels | indent 4 }} +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.prometheus.server.service.clusterIP }} + clusterIP: {{ .Values.prometheus.server.service.clusterIP }} +{{- end }} +{{- if .Values.prometheus.server.service.externalIPs }} + externalIPs: +{{ toYaml .Values.prometheus.server.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.prometheus.server.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.prometheus.server.service.loadBalancerIP }} +{{- end }} +{{- if .Values.prometheus.server.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.prometheus.server.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - name: http + port: {{ .Values.prometheus.server.service.servicePort }} + protocol: TCP + targetPort: 9090 + {{- if .Values.prometheus.server.service.nodePort }} + nodePort: {{ .Values.prometheus.server.service.nodePort }} + {{- end }} + {{- if .Values.prometheus.server.service.gRPC.enabled }} + - name: grpc + port: {{ .Values.prometheus.server.service.gRPC.servicePort }} + protocol: TCP + targetPort: 10901 + {{- if .Values.prometheus.server.service.gRPC.nodePort }} + nodePort: {{ .Values.prometheus.server.service.gRPC.nodePort }} + {{- end }} + {{- end }} + selector: + {{- if and .Values.prometheus.server.statefulSet.enabled .Values.prometheus.server.service.statefulsetReplica.enabled }} + statefulset.kubernetes.io/pod-name: {{ .Release.Name }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.server.service.statefulsetReplica.replica }} + {{- else -}} + {{- include "prometheus.server.matchLabels" . | nindent 4 }} +{{- if .Values.prometheus.server.service.sessionAffinity }} + sessionAffinity: {{ .Values.prometheus.server.service.sessionAffinity }} +{{- end }} + {{- end }} + type: "{{ .Values.prometheus.server.service.type }}" +{{- end -}} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-serviceaccount.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-serviceaccount.yaml new file mode 100644 index 0000000000..17ee234bb0 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-serviceaccount.yaml @@ -0,0 +1,17 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.serviceAccounts.server.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.serviceAccountName.server" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.prometheus.serviceAccounts.server.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-statefulset.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-statefulset.yaml new file mode 100644 index 0000000000..aba286811f --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-statefulset.yaml @@ -0,0 +1,227 @@ +{{ if .Values.global.prometheus.enabled }} +{{- if .Values.prometheus.server.enabled -}} +{{- if .Values.prometheus.server.statefulSet.enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: +{{- if .Values.prometheus.server.statefulSet.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.statefulSet.annotations | indent 4 }} +{{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.prometheus.server.statefulSet.labels}} + {{ toYaml .Values.prometheus.server.statefulSet.labels | nindent 4 }} + {{- end}} + name: {{ template "prometheus.server.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "prometheus.server.fullname" . }}-headless + selector: + matchLabels: + {{- include "prometheus.server.matchLabels" . | nindent 6 }} + replicas: {{ .Values.prometheus.server.replicaCount }} + podManagementPolicy: {{ .Values.prometheus.server.statefulSet.podManagementPolicy }} + template: + metadata: + {{- if .Values.prometheus.server.podAnnotations }} + annotations: +{{ toYaml .Values.prometheus.server.podAnnotations | indent 8 }} + {{- end }} + labels: + {{- include "prometheus.server.labels" . | nindent 8 }} + {{- with .Values.global.additionalLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.server.statefulSet.labels}} + {{ toYaml .Values.prometheus.server.statefulSet.labels | nindent 8 }} + {{- end}} + spec: +{{- if .Values.prometheus.server.priorityClassName }} + priorityClassName: "{{ .Values.prometheus.server.priorityClassName }}" +{{- end }} +{{- if .Values.prometheus.server.schedulerName }} + schedulerName: "{{ .Values.prometheus.server.schedulerName }}" +{{- end }} + serviceAccountName: {{ template "prometheus.serviceAccountName.server" . }} + containers: + {{- if .Values.prometheus.configmapReload.prometheus.enabled }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }}-{{ .Values.prometheus.configmapReload.prometheus.name }} + image: "{{ .Values.prometheus.configmapReload.prometheus.image.repository }}:{{ .Values.prometheus.configmapReload.prometheus.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.configmapReload.prometheus.image.pullPolicy }}" + args: + - --watched-dir=/etc/config + - --reload-url=http://127.0.0.1:9090{{ .Values.prometheus.server.prefixURL }}/-/reload + {{- range $key, $value := .Values.prometheus.configmapReload.prometheus.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraVolumeDirs }} + - --watched-dir={{ . }} + {{- end }} + resources: +{{ toYaml .Values.prometheus.configmapReload.prometheus.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + readOnly: true + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + - name: {{ template "prometheus.name" . }}-{{ .Values.prometheus.server.name }} + image: "{{ .Values.prometheus.server.image.repository }}:{{ .Values.prometheus.server.image.tag }}" + imagePullPolicy: "{{ .Values.prometheus.server.image.pullPolicy }}" + {{- if .Values.prometheus.server.env }} + env: +{{ toYaml .Values.prometheus.server.env | indent 12}} + {{- end }} + args: + {{- if .Values.prometheus.server.retention }} + - --storage.tsdb.retention.time={{ .Values.prometheus.server.retention }} + {{- end }} + - --config.file={{ .Values.prometheus.server.configPath }} + - --storage.tsdb.path={{ .Values.prometheus.server.persistentVolume.mountPath }} + - --web.console.libraries=/etc/prometheus/console_libraries + - --web.console.templates=/etc/prometheus/consoles + {{- range .Values.prometheus.server.extraFlags }} + - --{{ . }} + {{- end }} + {{- range $key, $value := .Values.prometheus.server.extraArgs }} + - --{{ $key }}={{ $value }} + {{- end }} + {{- if .Values.prometheus.server.baseURL }} + - --web.external-url={{ .Values.prometheus.server.baseURL }} + {{- end }} + ports: + - containerPort: 9090 + readinessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/ready + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.readinessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.readinessProbeTimeout }} + livenessProbe: + httpGet: + path: {{ .Values.prometheus.server.prefixURL }}/-/healthy + port: 9090 + initialDelaySeconds: {{ .Values.prometheus.server.livenessProbeInitialDelay }} + timeoutSeconds: {{ .Values.prometheus.server.livenessProbeTimeout }} + resources: +{{ toYaml .Values.prometheus.server.resources | indent 12 }} + volumeMounts: + - name: config-volume + mountPath: /etc/config + - name: storage-volume + mountPath: {{ .Values.prometheus.server.persistentVolume.mountPath }} + subPath: "{{ .Values.prometheus.server.persistentVolume.subPath }}" + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.prometheus.server.extraVolumeMounts }} + {{ toYaml .Values.prometheus.server.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.prometheus.server.sidecarContainers }} + {{- toYaml .Values.prometheus.server.sidecarContainers | nindent 8 }} + {{- end }} + {{- if .Values.prometheus.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.prometheus.imagePullSecrets | indent 2 }} + {{- end }} + {{- if .Values.prometheus.server.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.server.nodeSelector | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.securityContext }} + securityContext: +{{ toYaml .Values.prometheus.server.securityContext | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.server.tolerations | indent 8 }} + {{- end }} + {{- if .Values.prometheus.server.affinity }} + affinity: +{{ toYaml .Values.prometheus.server.affinity | indent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.prometheus.server.terminationGracePeriodSeconds }} + volumes: + - name: config-volume + configMap: + name: {{ if .Values.prometheus.server.configMapOverrideName }}{{ .Release.Name }}-{{ .Values.prometheus.server.configMapOverrideName }}{{- else }}{{ template "prometheus.server.fullname" . }}{{- end }} + {{- range .Values.prometheus.server.extraHostPathMounts }} + - name: {{ .name }} + hostPath: + path: {{ .hostPath }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.configmapReload.prometheus.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraConfigmapMounts }} + - name: {{ $.Values.prometheus.server.name }}-{{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.prometheus.server.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- range .Values.prometheus.configmapReload.prometheus.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- if .Values.prometheus.server.extraVolumes }} +{{ toYaml .Values.prometheus.server.extraVolumes | indent 8}} +{{- end }} +{{- if .Values.prometheus.server.persistentVolume.enabled }} + volumeClaimTemplates: + - metadata: + name: storage-volume + {{- if .Values.prometheus.server.persistentVolume.annotations }} + annotations: +{{ toYaml .Values.prometheus.server.persistentVolume.annotations | indent 10 }} + {{- end }} + spec: + accessModes: +{{ toYaml .Values.prometheus.server.persistentVolume.accessModes | indent 10 }} + resources: + requests: + storage: "{{ .Values.prometheus.server.persistentVolume.size }}" + {{- if .Values.prometheus.server.persistentVolume.storageClass }} + {{- if (eq "-" .Values.prometheus.server.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.prometheus.server.persistentVolume.storageClass }}" + {{- end }} + {{- end }} +{{- else }} + - name: storage-volume + emptyDir: {} +{{- end }} +{{- end }} +{{- end }} +{{ end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-vpa.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-vpa.yaml new file mode 100644 index 0000000000..25a61f2532 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/prometheus-server-vpa.yaml @@ -0,0 +1,22 @@ +{{- if and (.Values.global.prometheus.enabled) (.Values.prometheus.server.enabled) (.Values.prometheus.server.verticalAutoscaler.enabled) }} +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + labels: + {{- include "prometheus.server.labels" . | nindent 4 }} + name: {{ template "prometheus.server.fullname" . }}-vpa + namespace: {{ .Release.Namespace }} +spec: + targetRef: + apiVersion: apps/v1 +{{- if .Values.prometheus.server.statefulSet.enabled }} + kind: StatefulSet +{{- else }} + kind: Deployment +{{- end }} + name: {{ template "prometheus.server.fullname" . }} + updatePolicy: + updateMode: {{ .Values.prometheus.server.verticalAutoscaler.updateMode | default "Off" | quote }} + resourcePolicy: + containerPolicies: {{ .Values.prometheus.server.verticalAutoscaler.containerPolicies | default list | toYaml | trim | nindent 4 }} +{{- end }} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/tests/_helpers.tpl b/charts/kubecost/cost-analyzer/2.3.5/templates/tests/_helpers.tpl new file mode 100644 index 0000000000..8c1e53a560 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/tests/_helpers.tpl @@ -0,0 +1,5 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "kubecost.test.annotations" -}} +helm.sh/hook: test +{{- end -}} diff --git a/charts/kubecost/cost-analyzer/2.3.5/templates/tests/basic-health.yaml b/charts/kubecost/cost-analyzer/2.3.5/templates/tests/basic-health.yaml new file mode 100644 index 0000000000..e092b54fd2 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/templates/tests/basic-health.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: basic-health + namespace: {{ .Release.Namespace }} + annotations: + {{- include "kubecost.test.annotations" . | nindent 4 }} + labels: + app: basic-health + app.kubernetes.io/name: basic-health + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + automountServiceAccountToken: false + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + containers: + - name: test-kubecost + image: alpine/k8s:1.26.9 + securityContext: + privileged: false + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + command: + - /bin/sh + args: + - -c + - >- + svc="{{ .Release.Name }}-cost-analyzer"; + echo Getting current Kubecost state.; + response=$(curl -sL http://${svc}:9090/model/getConfigs); + code=$(echo ${response} | jq .code); + if [ "$code" -eq 200 ]; then + echo "Got Kubecost working configuration. Successful." + exit 0 + else + echo "Failed to fetch Kubecost configuration. Response was $response" + exit 1 + fi diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-agent.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-agent.yaml new file mode 100644 index 0000000000..39320e0edd --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-agent.yaml @@ -0,0 +1,113 @@ + +# Kubecost running as an Agent is designed for external hosting. The current setup deploys a +# kubecost-agent pod, low data retention prometheus server + thanos sidecar, and node-exporter. +networkCosts: + enabled: false + # config: + # services: + # amazon-web-services: true + # google-cloud-services: true + # azure-cloud-services: true +thanos: + storeSecretName: kubecost-agent-object-store + +global: + thanos: + enabled: false + grafana: + enabled: false + proxy: false +# Agent enables specific features designed to enhance the metrics exporter deployment +# with enhancements designed for external hosting. +# agent: true +# agentKeySecretName: kubecost-agent-object-store +agentCsi: + enabled: false + secretProvider: + name: kubecost-agent-object-store-secretprovider + provider: + parameters: {} + secretObjects: {} + +kubecostFrontend: + enabled: false + +# Exporter Pod +# kubecostMetrics: +# exporter: +# enabled: true +# exportClusterInfo: true +# exportClusterCache: true + +# Prometheus defaults to low retention (10h), disables KSM, and attaches a thanos-sidecar +# for exporting metrics. +prometheus: + nodeExporter: + enabled: false + extraScrapeConfigs: | + - job_name: kubecost-agent + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - kubecost-agent-agent + type: 'A' + port: 9005 + - job_name: kubecost-networking + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_pod_label_app] + action: keep + regex: {{ template "cost-analyzer.networkCostsName" . }} + server: + retention: 50h + # retentionSize: 1Gi + extraArgs: + storage.tsdb.min-block-duration: 2h + storage.tsdb.max-block-duration: 2h + securityContext: + runAsNonRoot: true + runAsUser: 1001 + extraSecretMounts: + - name: object-store-volume + mountPath: /etc/thanos/config + readOnly: true + secretName: kubecost-agent-object-store + enableAdminApi: true + sidecarContainers: + - name: thanos-sidecar + image: thanosio/thanos:v0.34.0 + securityContext: + runAsNonRoot: true + runAsUser: 1001 + args: + - sidecar + - --log.level=debug + - --tsdb.path=/data/ + - --prometheus.url=http://127.0.0.1:9090 + - --objstore.config-file=/etc/thanos/config/object-store.yaml + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + ports: + - name: sidecar-http + containerPort: 10902 + - name: grpc + containerPort: 10901 + - name: cluster + containerPort: 10900 + volumeMounts: + - name: config-volume + mountPath: /etc/prometheus + - name: storage-volume + mountPath: /data + subPath: "" + - name: object-store-volume + mountPath: /etc/thanos/config diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-amp.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-amp.yaml new file mode 100644 index 0000000000..4242ba3002 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-amp.yaml @@ -0,0 +1,20 @@ +global: + amp: + enabled: true + prometheusServerEndpoint: http://localhost:8005/workspaces/$ + remoteWriteService: https://aps-workspaces.us-west-2.amazonaws.com/workspaces/$/api/v1/remote_write + sigv4: + region: us-west-2 + +sigV4Proxy: + region: us-west-2 + host: aps-workspaces.us-west-2.amazonaws.com + +kubecostProductConfigs: + clusterName: AWS-cluster-one + +prometheus: + server: + global: + external_labels: + cluster_id: AWS-cluster-one \ No newline at end of file diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-cloud-agent.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-cloud-agent.yaml new file mode 100644 index 0000000000..3f24369253 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-cloud-agent.yaml @@ -0,0 +1,45 @@ +# Kubecost running as an Agent is designed for external hosting. The current setup deploys a +# kubecost-agent pod and prometheus server +global: + thanos: + enabled: false + grafana: + enabled: false + proxy: false + +# Cloud Agent enables specific features designed to enhance the metrics exporter deployment +# with enhancements designed for Kubecost Cloud +cloudAgent: true +cloudAgentKey: "" + +# No Grafana configuration is required. +grafana: + sidecar: + dashboards: + enabled: false + datasources: + defaultDatasourceEnabled: false + +# Exporter Pod +kubecostMetrics: + exporter: + enabled: true + exportClusterInfo: false + exportClusterCache: false + +# Disable KSM and NodeExporter (?) +prometheus: + nodeExporter: + enabled: false + extraScrapeConfigs: | + - job_name: kubecost-cloud-agent + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ .Release.Name }}-cloud-agent + type: 'A' + port: 9005 diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-custom-pricing.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-custom-pricing.yaml new file mode 100644 index 0000000000..82a0c55408 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-custom-pricing.yaml @@ -0,0 +1,17 @@ +pricingCsv: + enabled: true + location: + URI: /var/kubecost-csv/custom-pricing.csv # local configMap or s3://bucket/path/custom-pricing.csv + # provider: "AWS" + # region: "us-east-1" + # URI: s3://kc-csv-test/pricing_schema.csv # a valid file URI + # csvAccessCredentials: pricing-schema-access-secret + +# when using configmap: kubectl create configmap -n kubecost csv-pricing --from-file custom-pricing.csv +extraVolumes: +- name: kubecost-csv + configMap: + name: csv-pricing +extraVolumeMounts: +- name: kubecost-csv + mountPath: /var/kubecost-csv diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-eks-cost-monitoring.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-eks-cost-monitoring.yaml new file mode 100644 index 0000000000..8ccaf08ca5 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-eks-cost-monitoring.yaml @@ -0,0 +1,43 @@ +# grafana is disabled by default, but can be enabled by setting the following values. +# or proxy to an existing grafana: https://docs.kubecost.com/install-and-configure/advanced-configuration/custom-grafana +global: + grafana: + enabled: false + proxy: false +# grafana: +# image: +# repository: YOUR_REGISTRY/grafana +# sidecar: +# image: +# repository: YOUR_REGISTRY/k8s-sidecar + +kubecostFrontend: + image: public.ecr.aws/kubecost/frontend + +kubecostModel: + image: public.ecr.aws/kubecost/cost-model + +forecasting: + fullImageName: public.ecr.aws/kubecost/kubecost-modeling:v0.1.15 + +networkCosts: + image: + repository: public.ecr.aws/kubecost/kubecost-network-costs + +clusterController: + image: + repository: public.ecr.aws/kubecost/cluster-controller + +prometheus: + server: + image: + repository: public.ecr.aws/kubecost/prometheus + + configmapReload: + prometheus: + image: + repository: public.ecr.aws/kubecost/prometheus-config-reloader + +reporting: + productAnalytics: false + diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-openshift.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-openshift.yaml new file mode 100644 index 0000000000..7c8ea13b31 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-openshift.yaml @@ -0,0 +1,25 @@ +global: + # Platforms is a higher-level abstraction for platform-specific values and settings. + platforms: + # Deploying to OpenShift (OCP) requires enabling this option. + openshift: + enabled: true # Deploy Kubecost to OpenShift. + route: + enabled: false # Create an OpenShift Route. + annotations: {} # Add annotations to the Route. + # host: kubecost.apps.okd4.example.com # Add a custom host for your Route. + # Create Security Context Constraint resources for the DaemonSets requiring additional privileges. + scc: + nodeExporter: false # Creates an SCC for Prometheus Node Exporter. This requires Node Exporter be enabled. + networkCosts: false # Creates an SCC for Kubecost network-costs. This requires network-costs be enabled. + # When OpenShift is enabled, the following securityContext will be applied to all resources unless they define their own. + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + +# networkCosts: +# enabled: true # Enable network costs. +# prometheus: +# nodeExporter: +# enabled: true # Enable Prometheus Node Exporter. diff --git a/charts/kubecost/cost-analyzer/2.3.5/values-windows-node-affinity.yaml b/charts/kubecost/cost-analyzer/2.3.5/values-windows-node-affinity.yaml new file mode 100644 index 0000000000..5770f0c128 --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values-windows-node-affinity.yaml @@ -0,0 +1,30 @@ +kubecostMetrics: + exporter: + nodeSelector: + kubernetes.io/os: linux + +nodeSelector: + kubernetes.io/os: linux + +networkCosts: + nodeSelector: + kubernetes.io/os: linux + +prometheus: + server: + nodeSelector: + kubernetes.io/os: linux + nodeExporter: + enabled: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux +grafana: + nodeSelector: + kubernetes.io/os: linux diff --git a/charts/kubecost/cost-analyzer/2.3.5/values.yaml b/charts/kubecost/cost-analyzer/2.3.5/values.yaml new file mode 100644 index 0000000000..8e4466553e --- /dev/null +++ b/charts/kubecost/cost-analyzer/2.3.5/values.yaml @@ -0,0 +1,3456 @@ +global: + # zone: cluster.local (use only if your DNS server doesn't live in the same zone as kubecost) + prometheus: + enabled: true # Kubecost depends on Prometheus data, it is not optional. When enabled: false, Prometheus will not be installed and you must configure your own Prometheus to scrape kubecost as well as provide the fqdn below. -- Warning: Before changing this setting, please read to understand the risks https://docs.kubecost.com/install-and-configure/install/custom-prom + fqdn: http://cost-analyzer-prometheus-server.default.svc # example address of a prometheus to connect to. Include protocol (http:// or https://) Ignored if enabled: true + # insecureSkipVerify: false # If true, kubecost will not check the TLS cert of prometheus + # queryServiceBasicAuthSecretName: dbsecret # kubectl create secret generic dbsecret -n kubecost --from-file=USERNAME --from-file=PASSWORD + # queryServiceBearerTokenSecretName: mcdbsecret # kubectl create secret generic mcdbsecret -n kubecost --from-file=TOKEN + + grafana: + enabled: true # If false, Grafana will not be installed + domainName: cost-analyzer-grafana.default.svc # example grafana domain Ignored if enabled: true + scheme: "http" # http or https, for the domain name above. + proxy: true # If true, the kubecost frontend will route to your grafana through its service endpoint + # fqdn: cost-analyzer-grafana.default.svc + + # Enable only when you are using GCP Marketplace ENT listing. Learn more at https://console.cloud.google.com/marketplace/product/kubecost-public/kubecost-ent + gcpstore: + enabled: false + + # Google Cloud Managed Service for Prometheus + gmp: + # Remember to set up these parameters when install the Kubecost Helm chart with `global.gmp.enabled=true` if you want to use GMP self-deployed collection (Recommended) to utilize Kubecost scrape configs. + # If enabling GMP, it is highly recommended to utilize Google's distribution of Prometheus. + # Learn more at https://cloud.google.com/stackdriver/docs/managed-prometheus/setup-unmanaged + # --set prometheus.server.image.repository="gke.gcr.io/prometheus-engine/prometheus" \ + # --set prometheus.server.image.tag="v2.35.0-gmp.2-gke.0" + enabled: false # If true, kubecost will be configured to use GMP Prometheus image and query from Google Cloud Managed Service for Prometheus. + prometheusServerEndpoint: http://localhost:8085/ # The prometheus service endpoint used by kubecost. The calls are forwarded through the GMP Prom proxy side car to the GMP database. + gmpProxy: + enabled: false + image: gke.gcr.io/prometheus-engine/frontend:v0.4.1-gke.0 # GMP Prometheus proxy image that serve as an endpoint to query metrics from GMP + imagePullPolicy: IfNotPresent + name: gmp-proxy + port: 8085 + projectId: YOUR_PROJECT_ID # example GCP project ID + + # Amazon Managed Service for Prometheus + amp: + enabled: false # If true, kubecost will be configured to remote_write and query from Amazon Managed Service for Prometheus. + prometheusServerEndpoint: http://localhost:8005/workspaces// # The prometheus service endpoint used by kubecost. The calls are forwarded through the SigV4Proxy side car to the AMP workspace. + remoteWriteService: https://aps-workspaces.us-west-2.amazonaws.com/workspaces//api/v1/remote_write # The remote_write endpoint for the AMP workspace. + sigv4: + region: us-west-2 + # access_key: ACCESS_KEY # AWS Access key + # secret_key: SECRET_KEY # AWS Secret key + # role_arn: ROLE_ARN # AWS role arn + # profile: PROFILE # AWS profile + + # Mimir Proxy to help Kubecost to query metrics from multi-tenant Grafana Mimir. + # Set `global.mimirProxy.enabled=true` and `global.prometheus.enabled=false` to enable Mimir Proxy. + # You also need to set `global.prometheus.fqdn=http://kubecost-cost-analyzer-mimir-proxy.kubecost.svc:8085/prometheus` + # or `global.prometheus.fqdn=http://{{ template "cost-analyzer.fullname" . }}-mimir-proxy.{{ .Release.Namespace }}.svc:8085/prometheus' + # Learn more at https://grafana.com/docs/mimir/latest/operators-guide/secure/authentication-and-authorization/#without-an-authenticating-reverse-proxy + mimirProxy: + enabled: false + name: mimir-proxy + image: nginxinc/nginx-unprivileged + port: 8085 + mimirEndpoint: $mimir_endpoint # Your Mimir query endpoint. If your Mimir query endpoint is http://example.com/prometheus, replace $mimir_endpoint with http://example.com/ + orgIdentifier: $your_tenant_ID # Your Grafana Mimir tenant ID + # basicAuth: + # username: user + # password: pwd + + notifications: + # Kubecost alerting configuration + # Ref: http://docs.kubecost.com/alerts + # alertConfigs: + # frontendUrl: http://localhost:9090 # optional, used for linkbacks + # globalSlackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX # optional, used for Slack alerts + # globalMsTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX # optional, used for Microsoft Teams alerts + # globalAlertEmails: + # - recipient@example.com + # - additionalRecipient@example.com + # globalEmailSubject: Custom Subject + # Alerts generated by kubecost, about cluster data + # alerts: + # Daily namespace budget alert on namespace `kubecost` + # - type: budget # supported: budget, recurringUpdate + # threshold: 50 # optional, required for budget alerts + # window: daily # or 1d + # aggregation: namespace + # filter: kubecost + # ownerContact: # optional, overrides globalAlertEmails default + # - owner@example.com + # - owner2@example.com + # # optional, used for alert-specific Slack and Microsoft Teams alerts + # slackWebhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX + # msTeamsWebhookUrl: https://xxxxx.webhook.office.com/webhookb2/XXXXXXXXXXXXXXXXXXXXXXXX/IncomingWebhook/XXXXXXXXXXXXXXXXXXXXXXXX + + # Daily cluster budget alert on cluster `cluster-one` + # - type: budget + # threshold: 200.8 # optional, required for budget alerts + # window: daily # or 1d + # aggregation: cluster + # filter: cluster-one # does not accept csv + + # Recurring weekly update (weeklyUpdate alert) + # - type: recurringUpdate + # window: weekly # or 7d + # aggregation: namespace + # filter: '*' + + # Recurring weekly namespace update on kubecost namespace + # - type: recurringUpdate + # window: weekly # or 7d + # aggregation: namespace + # filter: kubecost + + # Spend Change Alert + # - type: spendChange # change relative to moving avg + # relativeThreshold: 0.20 # Proportional change relative to baseline. Must be greater than -1 (can be negative) + # window: 1d # accepts ‘d’, ‘h’ + # baselineWindow: 30d # previous window, offset by window + # aggregation: namespace + # filter: kubecost, default # accepts csv + + # Health Score Alert + # - type: health # Alerts when health score changes by a threshold + # window: 10m + # threshold: 5 # Send Alert if health scores changes by 5 or more + + # Kubecost Health Diagnostic + # - type: diagnostic # Alerts when kubecost is unable to compute costs - ie: Prometheus unreachable + # window: 10m + + alertmanager: # Supply an alertmanager FQDN to receive notifications from the app. + enabled: false # If true, allow kubecost to write to your alertmanager + fqdn: http://cost-analyzer-prometheus-server.default.svc # example fqdn. Ignored if prometheus.enabled: true + + # Set saved Cost Allocation report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + savedReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Saved Report 0" + window: "today" + aggregateBy: "namespace" + chartDisplay: "category" + idle: "separate" + rate: "cumulative" + accumulate: false # daily resolution + filters: # Ref: https://docs.kubecost.com/apis/filters-api + - key: "cluster" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: ":" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "dev" + - title: "Example Saved Report 1" + window: "month" + aggregateBy: "controllerKind" + chartDisplay: "category" + idle: "share" + rate: "monthly" + accumulate: false + filters: # Ref: https://docs.kubecost.com/apis/filters-api + - key: "namespace" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: "!:" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "kubecost" + - title: "Example Saved Report 2" + window: "2020-11-11T00:00:00Z,2020-12-09T23:59:59Z" + aggregateBy: "service" + chartDisplay: "category" + idle: "hide" + rate: "daily" + accumulate: true # entire window resolution + filters: [] # if no filters, specify empty array + + # Set saved Asset report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + assetReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Asset Report 0" + window: "today" + aggregateBy: "type" + accumulate: false # daily resolution + filters: + - property: "cluster" + value: "cluster-one" + + # Set saved Advanced report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + advancedReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Example Advanced Report 0" + window: "7d" + aggregateBy: "namespace" + filters: # same as allocation api filters Ref: https://docs.kubecost.com/apis/filters-api + - key: "cluster" # Ref: https://docs.kubecost.com/apis/filters-api#allocation-apis-request-sizing-v2-api + operator: ":" # Ref: https://docs.kubecost.com/apis/filters-api#filter-operators + value: "dev" + cloudBreakdown: "service" + cloudJoin: "label:kubernetes_namespace" + + # Set saved Cloud Cost report(s) accessible from /reports + # Ref: http://docs.kubecost.com/saved-reports + cloudCostReports: + enabled: false # If true, overwrites report parameters set through UI + reports: + - title: "Cloud Cost Report 0" + window: "today" + aggregateBy: "service" + accumulate: false # daily resolution + # filters: + # - property: "service" + # value: "service1" # corresponds to a value to filter cloud cost aggregate by service data on. + + podAnnotations: {} + # iam.amazonaws.com/role: role-arn + + # Applies these labels to all Deployments, StatefulSets, DaemonSets, and their pod templates. + additionalLabels: {} + + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + fsGroup: 1001 + runAsGroup: 1001 + runAsUser: 1001 + fsGroupChangePolicy: OnRootMismatch + containerSecurityContext: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + + # Platforms is a higher-level abstraction for platform-specific values and settings. + platforms: + # Deploying to OpenShift (OCP) requires enabling this option. + openshift: + enabled: false # Deploy Kubecost to OpenShift. + route: + enabled: false # Create an OpenShift Route. + annotations: {} # Add annotations to the Route. + # host: kubecost.apps.okd4.example.com # Add a custom host for your Route. + # Create Security Context Constraint resources for the DaemonSets requiring additional privileges. + scc: + nodeExporter: false # Creates an SCC for Prometheus Node Exporter. This requires Node Exporter be enabled. + networkCosts: false # Creates an SCC for Kubecost network-costs. This requires network-costs be enabled. + # When OpenShift is enabled, the following securityContext will be applied to all resources unless they define their own. + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + # Set options for deploying with CI/CD tools like Argo CD. + cicd: + enabled: false # Set to true when using affected CI/CD tools for access to the below configuration options. + skipSanityChecks: false # If true, skip all sanity/existence checks for resources like Secrets. + + ## Kubecost Integrations + ## Ref: https://docs.kubecost.com/integrations + ## + integrations: + postgres: + enabled: false + runInterval: "12h" # How frequently to run the integration. + databaseHost: "" # REQUIRED. ex: my.postgres.database.azure.com + databasePort: "" # REQUIRED. ex: 5432 + databaseName: "" # REQUIRED. ex: postgres + databaseUser: "" # REQUIRED. ex: myusername + databasePassword: "" # REQUIRED. ex: mypassword + databaseSecretName: "" # OPTIONAL. Specify your own k8s secret containing the above credentials. Must have key "creds.json". + + ## Configure what Postgres table to write to, and what parameters to pass + ## when querying Kubecost's APIs. Ensure all parameters are enclosed in + ## quotes. Ref: https://docs.kubecost.com/apis/apis-overview + queryConfigs: + allocations: [] + # - databaseTable: "kubecost_allocation_data" + # window: "7d" + # aggregate: "namespace" + # idle: "true" + # shareIdle: "true" + # shareNamespaces: "kubecost,kube-system" + # shareLabels: "" + # - databaseTable: "kubecost_allocation_data_by_cluster" + # window: "10d" + # aggregate: "cluster" + # idle: "true" + # shareIdle: "false" + # shareNamespaces: "" + # shareLabels: "" + assets: [] + # - databaseTable: "kubecost_assets_data" + # window: "7d" + # aggregate: "cluster" + cloudCosts: [] + # - databaseTable: "kubecost_cloudcosts_data" + # window: "7d" + # aggregate: "service" + +## Provide a name override for the chart. +# nameOverride: "" +## Provide a full name override option for the chart. +# fullnameOverride: "" + +## This flag is only required for users upgrading to a new version of Kubecost. +## The flag is used to ensure users are aware of important +## (potentially breaking) changes included in the new version. +## +upgrade: + toV2: false + +# generated at http://kubecost.com/install, used for alerts tracking and free trials +kubecostToken: # "" + +# Advanced pipeline for custom prices, enterprise key required +pricingCsv: + enabled: false + location: + provider: "AWS" + region: "us-east-1" + URI: s3://kc-csv-test/pricing_schema.csv # a valid file URI + csvAccessCredentials: pricing-schema-access-secret + +# SAML integration for user management and RBAC, enterprise key required +# Ref: https://github.com/kubecost/docs/blob/main/user-management.md +saml: + enabled: false + # secretName: "kubecost-authzero" + # metadataSecretName: "kubecost-authzero-metadata" # One of metadataSecretName or idpMetadataURL must be set. defaults to metadataURL if set + # idpMetadataURL: "https://dev-elu2z98r.auth0.com/samlp/metadata/c6nY4M37rBP0qSO1IYIqBPPyIPxLS8v2" + # appRootURL: "http://localhost:9090" # sample URL + # authTimeout: 1440 # number of minutes the JWT will be valid + # redirectURL: "https://dev-elu2z98r.auth0.com/v2/logout" # callback URL redirected to after logout + # audienceURI: "http://localhost:9090" # by convention, the same as the appRootURL, but any string uniquely identifying kubecost to your samp IDP. Optional if you follow the convention + # nameIDFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" If your SAML provider requires a specific nameid format + # isGLUUProvider: false # An additional URL parameter must be appended for GLUU providers + # encryptionCertSecret: "kubecost-saml-cert" # k8s secret where the x509 certificate used to encrypt an Okta saml response is stored + # decryptionKeySecret: "kubecost-sank-decryption-key" # k8s secret where the private key associated with the encryptionCertSecret is stored + # authSecret: "random-string" # value of SAML secret used to issue tokens, will be autogenerated as random string if not provided + # authSecretName: "kubecost-saml-secret" # name of k8s secret where the authSecret will be stored, defaults to "kubecost-saml-secret" if not provided + rbac: + enabled: false + # groups: + # - name: admin + # enabled: false # if admin is disabled, all SAML users will be able to make configuration changes to the kubecost frontend + # assertionName: "http://schemas.auth0.com/userType" # a SAML Assertion, one of whose elements has a value that matches on of the values in assertionValues + # assertionValues: + # - "admin" + # - "superusers" + # - name: readonly + # enabled: false # if readonly is disabled, all users authorized on SAML will default to readonly + # assertionName: "http://schemas.auth0.com/userType" + # assertionValues: + # - "readonly" + # - name: editor + # enabled: true # if editor is enabled, editors will be allowed to edit reports/alerts scoped to them, and act as readers otherwise. Users will never default to editor. + # assertionName: "http://schemas.auth0.com/userType" + # assertionValues: + # - "editor" + +oidc: + enabled: false + clientID: "" # application/client client_id parameter obtained from provider, used to make requests to server + clientSecret: "" # application/client client_secret parameter obtained from provider, used to make requests to server + # secretName: "kubecost-oidc-secret" # k8s secret where clientsecret will be stored + # For use to provide a custom OIDC Secret. Overrides the usage of oidc.clientSecret and oidc.secretName. + # Should contain the field directly. + # Can be created using raw k8s secrets, external secrets, sealed secrets, or any other method. + existingCustomSecret: + enabled: false + name: "" # name of the secret containing the client secret + + # authURL: "https://my.auth.server/authorize" # endpoint for login to auth server + # loginRedirectURL: "http://my.kubecost.url/model/oidc/authorize" # Kubecost url configured in provider for redirect after authentication + # discoveryURL: "https://my.auth.server/.well-known/openid-configuration" # url for OIDC endpoint discovery + skipOnlineTokenValidation: false # if true, will skip accessing OIDC introspection endpoint for online token verification, and instead try to locally validate JWT claims + useClientSecretPost: false # if true, client secret will specifically only use client_secret_post method, otherwise it will attempt to send the secret in both the header and the body. + # hostedDomain: "example.com" # optional, blocks access to the auth domain specified in the hd claim of the provider ID token + rbac: + enabled: false + # groups: + # - name: admin + # enabled: false # if admin is disabled, all authenticated users will be able to make configuration changes to the kubecost frontend + # claimName: "roles" # Kubecost matches this string against the JWT's payload key containing RBAC info (this value is unique across identity providers) + # claimValues: # Kubecost matches these strings with the roles created in your identity provider + # - "admin" + # - "superusers" + # - name: readonly + # enabled: false # if readonly is disabled, all authenticated users will default to readonly + # claimName: "roles" + # claimValues: + # - "readonly" + # - name: editor + # enabled: false # if editor is enabled, editors will be allowed to edit reports/alerts scoped to them, and act as readers otherwise. Users will never default to editor. + # claimName: "roles" + # claimValues: + # - "editor" + +## Adds the HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables to all +## containers. Typically used in environments that have firewall rules which +## prevent kubecost from accessing cloud provider resources. +## Ref: https://www.oreilly.com/library/view/security-with-go/9781788627917/5ea6a02b-3d96-44b1-ad3c-6ab60fcbbe4f.xhtml +## +systemProxy: + enabled: false + httpProxyUrl: "" + httpsProxyUrl: "" + noProxy: "" + +# imagePullSecrets: +# - name: "image-pull-secret" + +# imageVersion uses the base image name (image:) but overrides the version +# pulled. It should be avoided. If non-default behavior is needed, use +# fullImageName for the relevant component. +# imageVersion: + +kubecostFrontend: + enabled: true + deployMethod: singlepod # haMode or singlepod - haMode is currently only supported with Enterprise tier + haReplicas: 2 # only used with haMode + image: "gcr.io/kubecost1/frontend" + imagePullPolicy: IfNotPresent + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for the frontend. + # fullImageName: + + # extraEnv: + # - name: NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE + # value: "1" + # securityContext: + # readOnlyRootFilesystem: true + resources: + requests: + cpu: "10m" + memory: "55Mi" + # limits: + # cpu: "100m" + # memory: "256Mi" + deploymentStrategy: {} + # rollingUpdate: + # maxSurge: 1 + # maxUnavailable: 1 + # type: RollingUpdate + + # Define a readiness probe for the Kubecost frontend container. + readinessProbe: + enabled: true + initialDelaySeconds: 1 + periodSeconds: 5 + failureThreshold: 6 + + # Define a liveness probe for the Kubecost frontend container. + livenessProbe: + enabled: true + initialDelaySeconds: 1 + periodSeconds: 5 + failureThreshold: 6 + ipv6: + enabled: true # disable if the cluster does not support ipv6 + # timeoutSeconds: 600 # should be rarely used, but can be increased if needed + # allow customizing nginx-conf server block + # extraServerConfig: |- + # proxy_busy_buffers_size 512k; + # proxy_buffers 4 512k; + # proxy_buffer_size 256k; + # large_client_header_buffers 4 64k; + # hideDiagnostics: false # useful if the primary is not monitored. Supported in limited environments. + # hideOrphanedResources: false # OrphanedResources works on the primary-cluster's cloud-provider only. + + # set to true to set all upstreams to use ..svc.cluster.local instead of just . + useDefaultFqdn: false +# api: +# fqdn: kubecost-api.kubecost.svc.cluster.local:9001 +# model: +# fqdn: kubecost-model.kubecost.svc.cluster.local:9003 +# forecasting: +# fqdn: kubecost-forcasting.kubecost.svc.cluster.local:5000 +# aggregator: +# fqdn: kubecost-aggregator.kubecost.svc.cluster.local:9004 +# cloudCost: +# fqdn: kubecost-cloud-cost.kubecost.svc.cluster.local:9005 +# multiClusterDiagnostics: +# fqdn: kubecost-multi-diag.kubecost.svc.cluster.local:9007 +# clusterController: +# fqdn: cluster-controller.kubecost.svc.cluster.local:9731 + +# Kubecost Metrics deploys a separate pod which will emit kubernetes specific metrics required +# by the cost-model. This pod is designed to remain active and decoupled from the cost-model itself. +# However, disabling this service/pod deployment will flag the cost-model to emit the metrics instead. +kubecostMetrics: + # emitPodAnnotations: false + # emitNamespaceAnnotations: false + # emitKsmV1Metrics: true # emit all KSM metrics in KSM v1. + # emitKsmV1MetricsOnly: false # emit only the KSM metrics missing from KSM v2. Advanced users only. + + # Optional + # The metrics exporter is a separate deployment and service (for prometheus scrape auto-discovery) + # which emits metrics cost-model relies on. Enabling this deployment also removes the KSM dependency + # from the cost-model. If the deployment is not enabled, the metrics will continue to be emitted from + # the cost-model. + exporter: + enabled: false + port: 9005 + # Adds the default Prometheus scrape annotations to the metrics exporter service. + # Set to false and use service.annotations (below) to set custom scrape annotations. + prometheusScrape: true + resources: {} + # requests: + # cpu: "200m" + # memory: "55Mi" + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + tolerations: [] + + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + affinity: {} + + service: + annotations: {} + + # Service Monitor for Kubecost Metrics + serviceMonitor: # the kubecost included prometheus uses scrapeConfigs and does not support service monitors. The following options assume an existing prometheus that supports serviceMonitors. + enabled: false + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + additionalLabels: {} + nodeSelector: {} + extraArgs: [] + +sigV4Proxy: + image: public.ecr.aws/aws-observability/aws-sigv4-proxy:latest + imagePullPolicy: IfNotPresent + name: aps + port: 8005 + region: us-west-2 # The AWS region + host: aps-workspaces.us-west-2.amazonaws.com # The hostname for AMP service. + # role_arn: arn:aws:iam:::role/role-name # The AWS IAM role to assume. + extraEnv: # Pass extra env variables to sigV4Proxy + # - name: AWS_ACCESS_KEY_ID + # value: + # - name: AWS_SECRET_ACCESS_KEY + # value: + # Optional resource requests and limits for the sigV4proxy container. + resources: {} + +kubecostModel: + image: "gcr.io/kubecost1/cost-model" + imagePullPolicy: IfNotPresent + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for cost-model. + # fullImageName: + + # extraEnv: + # - name: SOME_VARIABLE + # value: "some_value" + # securityContext: + # readOnlyRootFilesystem: true + # Build local cost allocation cache + warmCache: false + # Run allocation ETL pipelines + etl: true + # Enable the ETL filestore backing storage + etlFileStoreEnabled: true + # The total number of days the ETL pipelines will build + # Set to 0 to disable daily ETL (not recommended) + etlDailyStoreDurationDays: 91 + # The total number of hours the ETL pipelines will build + # Set to 0 to disable hourly ETL (not recommended) + # Must be < prometheus server retention, otherwise empty data may overwrite + # known-good data + etlHourlyStoreDurationHours: 49 + # The total number of weeks the ETL pipelines will build + # Set to 0 to disable weekly ETL (not recommended) + # The default is 53 to ensure at least a year of coverage (371 days) + etlWeeklyStoreDurationWeeks: 53 + # For deploying kubecost in a cluster that does not self-monitor + etlReadOnlyMode: false + + # The name of the Secret containing a bucket config for ETL backup. + # etlBucketConfigSecret: + # The name of the Secret containing a bucket config for Federated storage. The contents should be stored + # under a key named federated-store.yaml. + # federatedStorageConfigSecret: "" + + # Installs Kubecost/OpenCost plugins + plugins: + enabled: false + install: + enabled: false + fullImageName: curlimages/curl:latest + securityContext: + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1001 + folder: /opt/opencost/plugin + + # leave this commented to always download most recent version of plugins + # version: + + # the list of enabled plugins + enabledPlugins: [] + # - datadog + + # pre-existing secret for plugin configuration + existingCustomSecret: + enabled: false + name: "" # name of the secret containing plugin config + + secretName: kubecost-plugin-secret + + # uncomment this to define plugin configuration via the values file + # configs: + # datadog: | + # { + # "datadog_site": "", + # "datadog_api_key": "", + # "datadog_app_key": "" + # } + + allocation: + # Enables or disables adding node labels to allocation data (i.e. workloads). + # Defaults to "true" and starts with a sensible includeList for basics like + # topology (e.g. zone, region) and instance type labels. + # nodeLabels: + # enabled: true + # includeList: "node.kubernetes.io/instance-type,topology.kubernetes.io/region,topology.kubernetes.io/zone" + + # Enables or disables the ContainerStats pipeline, used for quantile-based + # queries like for request sizing recommendations. + # ContainerStats provides support for quantile-based request right-sizing + # recommendations. + # + # It is disabled by default to avoid problems in extremely high-scale Thanos + # environments. If you would like to try quantile-based request-sizing + # recommendations, enable this! If you are in a high-scale environment, + # please monitor Kubecost logs, Thanos query logs, and Thanos load closely. + # We hope to make major improvements at scale here soon! + # + containerStatsEnabled: true # enabled by default as of v2.2.0 + + # max number of concurrent Prometheus queries + maxQueryConcurrency: 5 + resources: + requests: + cpu: "200m" + memory: "55Mi" + # limits: + # cpu: "800m" + # memory: "256Mi" + + # Define a readiness probe for the Kubecost cost-model container. + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + # Define a liveness probe for the Kubecost cost-model container. + livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + extraArgs: [] + + # Optional. A list of extra environment variables to be added to the cost-model container. + # extraEnv: [] + # - name: LOG_LEVEL + # value: trace + # - name: LOG_FORMAT + # value: json + + # creates an ingress directly to the model container, for API access + ingress: + enabled: false + # className: nginx + labels: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + paths: ["/"] + pathType: ImplementationSpecific + hosts: + - cost-analyzer-model.local + tls: [] + # - secretName: cost-analyzer-model-tls + # hosts: + # - cost-analyzer-model.local + utcOffset: "+00:00" + # Optional - add extra ports to the cost-model container. For kubecost development purposes only - not recommended for users. + extraPorts: [] + # - name: debug + # port: 40000 + # targetPort: 40000 + # containerPort: 40000 + +# etlUtils is a utility currently used by Kubecost internal support to implement specific functionality related to Thanos conversion. +etlUtils: + enabled: false + fullImageName: null + resources: {} + env: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +# Basic Kubecost ingress, more examples available at https://docs.kubecost.com/install-and-configure/install/ingress-examples +ingress: + enabled: false + # className: nginx + labels: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + paths: ["/"] # There's no need to route specifically to the pods-- we have an nginx deployed that handles routing + pathType: ImplementationSpecific + hosts: + - cost-analyzer.local + tls: [] + # - secretName: cost-analyzer-tls + # hosts: + # - cost-analyzer.local + +nodeSelector: {} + +tolerations: [] +# - key: "key" +# operator: "Equal|Exists" +# value: "value" +# effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + +affinity: {} + +topologySpreadConstraints: [] + +# If true, creates a PriorityClass to be used by the cost-analyzer pod +priority: + enabled: false + name: "" # Provide name of existing priority class only. If left blank, upstream chart will create one from default template. + +# If true, enable creation of NetworkPolicy resources. +networkPolicy: + enabled: false + denyEgress: true # create a network policy that denies egress from kubecost + sameNamespace: true # Set to true if cost analyzer and prometheus are on the same namespace +# namespace: kubecost # Namespace where prometheus is installed + + # Cost-analyzer specific vars using the new template + costAnalyzer: + enabled: false # If true, create a network policy for cost-analyzer + annotations: {} # annotations to be added to the network policy + additionalLabels: {} # additional labels to be added to the network policy + # Examples rules: + # ingressRules: + # - selectors: # allow ingress from self on all ports + # - podSelector: + # matchLabels: + # app.kubernetes.io/name: cost-analyzer + # - selectors: # allow egress access to prometheus + # - namespaceSelector: + # matchLabels: + # name: prometheus + # podSelector: + # matchLabels: + # app: prometheus + # ports: + # - protocol: TCP + # port: 9090 + # egressRules: + # - selectors: # restrict egress to inside cluster + # - namespaceSelector: {} + +## @param extraVolumes A list of volumes to be added to the pod +## +extraVolumes: [] +## @param extraVolumeMounts A list of volume mounts to be added to the pod +## +extraVolumeMounts: [] + +# Define persistence volume for cost-analyzer, more information at https://docs.kubecost.com/install-and-configure/install/storage +persistentVolume: + size: 32Gi + dbSize: 32.0Gi + enabled: true # Note that setting this to false means configurations will be wiped out on pod restart. + # storageClass: "-" # + # existingClaim: kubecost-cost-analyzer # a claim in the same namespace as kubecost + labels: {} + annotations: {} + # helm.sh/resource-policy: keep # https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource + + # Enables a separate PV specifically for ETL data. This should be avoided, but + # is kept for legacy compatibility. + dbPVEnabled: false + +service: + type: ClusterIP + port: 9090 + targetPort: 9090 + nodePort: {} + labels: {} + annotations: {} + # loadBalancerSourceRanges: [] + sessionAffinity: + enabled: false # Makes sure that connections from a client are passed to the same Pod each time, when set to `true`. You should set it when you enabled authentication through OIDC or SAML integration. + timeoutSeconds: 10800 + +prometheus: + ## Provide a full name override for Prometheus. + # fullnameOverride: "" + ## Provide a name override for Prometheus. + # nameOverride: "" + + rbac: + create: true # Create the RBAC resources for Prometheus. + + ## Define serviceAccount names for components. Defaults to component's fully qualified name. + ## + serviceAccounts: + alertmanager: + create: true + name: + nodeExporter: + create: true + name: + pushgateway: + create: true + name: + server: + create: true + name: + ## Prometheus server ServiceAccount annotations. + ## Can be used for AWS IRSA annotations when using Remote Write mode with Amazon Managed Prometheus. + annotations: {} + + ## Specify an existing ConfigMap to be used by Prometheus when using self-signed certificates. + ## + # selfsignedCertConfigMapName: "" + + imagePullSecrets: + # - name: "image-pull-secret" + + extraScrapeConfigs: | + - job_name: kubecost + honor_labels: true + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ template "cost-analyzer.serviceName" . }} + type: 'A' + port: 9003 + - job_name: kubecost-networking + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance] + action: keep + regex: kubecost + - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] + action: keep + regex: network-costs + - job_name: kubecost-aggregator + scrape_interval: 1m + scrape_timeout: 60s + metrics_path: /metrics + scheme: http + dns_sd_configs: + - names: + - {{ template "aggregator.serviceName" . }} + type: 'A' + {{- if or .Values.saml.enabled .Values.oidc.enabled }} + port: 9008 + {{- else }} + port: 9004 + {{- end }} + server: + # If clusterIDConfigmap is defined, instead use user-generated configmap with key CLUSTER_ID + # to use as unique cluster ID in kubecost cost-analyzer deployment. + # This overrides the cluster_id set in prometheus.server.global.external_labels. + # NOTE: This does not affect the external_labels set in prometheus config. + # clusterIDConfigmap: cluster-id-configmap + + ## Provide a full name override for the Prometheus server. + # fullnameOverride: "" + + ## Prometheus server container name + ## + enabled: true + name: server + sidecarContainers: + strategy: + type: Recreate + rollingUpdate: null + + ## Prometheus server container image + ## + image: + repository: quay.io/prometheus/prometheus + tag: v2.52.0 + pullPolicy: IfNotPresent + + ## prometheus server priorityClassName + ## + priorityClassName: "" + + ## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug + ## so that the various internal URLs are still able to access as they are in the default case. + ## (Optional) + prefixURL: "" + + ## External URL which can access alertmanager + ## Maybe same with Ingress host name + baseURL: "" + + ## Additional server container environment variables + ## + ## You specify this manually like you would a raw deployment manifest. + ## This means you can bind in environment variables from secrets. + ## + ## e.g. static environment variable: + ## - name: DEMO_GREETING + ## value: "Hello from the environment" + ## + ## e.g. secret environment variable: + ## - name: USERNAME + ## valueFrom: + ## secretKeyRef: + ## name: mysecret + ## key: username + env: [] + + extraFlags: + - web.enable-lifecycle + ## web.enable-admin-api flag controls access to the administrative HTTP API which includes functionality such as + ## deleting time series. This is disabled by default. + # - web.enable-admin-api + ## + ## storage.tsdb.no-lockfile flag controls BD locking + # - storage.tsdb.no-lockfile + ## + ## storage.tsdb.wal-compression flag enables compression of the write-ahead log (WAL) + # - storage.tsdb.wal-compression + + ## Path to a configuration file on prometheus server container FS + configPath: /etc/config/prometheus.yml + + global: + ## How frequently to scrape targets by default + ## + scrape_interval: 1m + ## How long until a scrape request times out + ## + scrape_timeout: 60s + ## How frequently to evaluate rules + ## + evaluation_interval: 1m + external_labels: + cluster_id: cluster-one # Each cluster should have a unique ID + ## https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write + ## + remoteWrite: {} + ## https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_read + ## + remoteRead: {} + + ## Additional Prometheus server container arguments + ## + extraArgs: + query.max-concurrency: 1 + query.max-samples: 100000000 + + ## Additional InitContainers to initialize the pod + ## + extraInitContainers: [] + + ## Additional Prometheus server Volume mounts + ## + extraVolumeMounts: [] + + ## Additional Prometheus server Volumes + ## + extraVolumes: [] + + ## Additional Prometheus server hostPath mounts + ## + extraHostPathMounts: [] + # - name: certs-dir + # mountPath: /etc/kubernetes/certs + # subPath: "" + # hostPath: /etc/kubernetes/certs + # readOnly: true + + extraConfigmapMounts: [] + # - name: certs-configmap + # mountPath: /prometheus + # subPath: "" + # configMap: certs-configmap + # readOnly: true + + ## Additional Prometheus server Secret mounts + # Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # subPath: "" + # secretName: prom-secret-files + # readOnly: true + + ## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.server.configMapOverrideName}} + ## Defining configMapOverrideName will cause templates/server-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configMapOverrideName: "" + + ingress: + ## If true, Prometheus server Ingress will be created + ## + enabled: false + # className: nginx + + ## Prometheus server Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## Prometheus server Ingress additional labels + ## + extraLabels: {} + + ## Prometheus server Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - prometheus.domain.com + # - domain.com/prometheus + + ## PathType determines the interpretation of the Path matching + pathType: "Prefix" + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## Prometheus server Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-server-tls + # hosts: + # - prometheus.domain.com + + ## Server Deployment Strategy type + # strategy: + # type: Recreate + + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for Prometheus server pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Pod affinity + ## + affinity: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + persistentVolume: + ## If true, Prometheus server will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## Prometheus server data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## Prometheus server data Persistent Volume annotations + ## + annotations: {} + # helm.sh/resource-policy: keep # https://helm.sh/docs/howto/charts_tips_and_tricks/#tell-helm-not-to-uninstall-a-resource + + ## Prometheus server data Persistent Volume existing claim name + ## Requires server.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## Prometheus server data Persistent Volume mount root path + ## + mountPath: /data + + ## Prometheus server data Persistent Volume size + ## + size: 32Gi + + ## Prometheus server data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## Prometheus server data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of Prometheus server data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + emptyDir: + sizeLimit: "" + + ## Annotations to be added to Prometheus server pods + ## + podAnnotations: {} + # iam.amazonaws.com/role: prometheus + + ## Annotations to be added to the Prometheus Server deployment + ## + deploymentAnnotations: {} + + ## Labels to be added to Prometheus server pods + ## + podLabels: {} + + ## Prometheus AlertManager configuration + ## + alertmanagers: [] + + ## Use a StatefulSet if replicaCount needs to be greater than 1 (see below) + ## + replicaCount: 1 + + statefulSet: + ## If true, use a statefulset instead of a deployment for pod management. + ## This allows to scale replicas to more than 1 pod + ## + enabled: false + + annotations: {} + labels: {} + podManagementPolicy: OrderedReady + + ## Alertmanager headless service to use for the statefulset + ## + headless: + annotations: {} + labels: {} + servicePort: 80 + + ## Prometheus server readiness and liveness probe initial delay and timeout + ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + ## + readinessProbeInitialDelay: 5 + readinessProbeTimeout: 3 + readinessProbeFailureThreshold: 3 + readinessProbeSuccessThreshold: 1 + livenessProbeInitialDelay: 5 + livenessProbeTimeout: 3 + livenessProbeFailureThreshold: 3 + livenessProbeSuccessThreshold: 1 + + ## Prometheus server resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 500m + # memory: 512Mi + + ## Vertical Pod Autoscaler config + ## Ref: https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler + verticalAutoscaler: + ## If true a VPA object will be created for the controller (either StatefulSet or Deployment, based on above configs) + enabled: false + ## Optional. Defaults to "Auto" if not specified. + # updateMode: "Auto" + ## Mandatory. Without, VPA will not be created. + # containerPolicies: + # - containerName: 'prometheus-server' + + ## Security context to be added to server pods + ## + securityContext: {} + # runAsUser: 1001 + # runAsNonRoot: true + # runAsGroup: 1001 + # fsGroup: 1001 + + containerSecurityContext: {} + + service: + annotations: {} + labels: {} + clusterIP: "" + # nodePort: "" + + ## List of IP addresses at which the Prometheus server service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 80 + sessionAffinity: None + type: ClusterIP + + ## Enable gRPC port on service to allow auto discovery with thanos-querier + gRPC: + enabled: false + servicePort: 10901 + # nodePort: 10901 + + ## If using a statefulSet (statefulSet.enabled=true), configure the + ## service to connect to a specific replica to have a consistent view + ## of the data. + statefulsetReplica: + enabled: false + replica: 0 + + ## Prometheus server pod termination grace period + ## + terminationGracePeriodSeconds: 300 + + ## Prometheus data retention period (default if not specified is 97 hours) + ## + ## Kubecost builds up its own persistent store of metric data on the + ## filesystem (usually a PV) and, when using ETL Backup and/or Federated + ## ETL, in more durable object storage like S3 or GCS. Kubecost's data + ## retention is _not_ tied to the configured Prometheus retention. + ## + ## For data durability, we recommend using ETL Backup instead of relying on + ## Prometheus retention. + ## + ## Lower retention values will affect Prometheus by reducing resource + ## consumption and increasing stability. It _must not_ be set below or equal + ## to kubecostModel.etlHourlyStoreDurationHours, otherwise empty data sets + ## may overwrite good data sets. For now, it must also be >= 49h for Daily + ## ETL stability. + ## + ## "ETL Rebuild" and "ETL Repair" is only possible on data available within + ## this retention window. This is an extremely rare operation. + ## + ## If you want maximum security in the event of a Kubecost agent + ## (cost-model) outage, increase this value. The current default of 97h is + ## intended to balance Prometheus stability and resource consumption + ## against the event of an outage in Kubecost which would necessitate a + ## version change. 4 days should provide enough time for most users to + ## notice a problem and initiate corrective action. + retention: 97h + # retentionSize: should be significantly greater than the storage used in the number of hours set in etlHourlyStoreDurationHours + + # Install Prometheus Alert Manager + alertmanager: + ## If false, alertmanager will not be installed + ## + enabled: false + + ## Provide a full name override for Prometheus alertmanager. + # fullnameOverride: "" + + strategy: + type: Recreate + rollingUpdate: null + + ## alertmanager container name + ## + name: alertmanager + + ## alertmanager container image + ## + image: + repository: quay.io/prometheus/alertmanager + tag: v0.27.0 + pullPolicy: IfNotPresent + + ## alertmanager priorityClassName + ## + priorityClassName: "" + + ## Additional alertmanager container arguments + ## + extraArgs: {} + + ## The URL prefix at which the container can be accessed. Useful in the case the '-web.external-url' includes a slug + ## so that the various internal URLs are still able to access as they are in the default case. + ## (Optional) + prefixURL: "" + + ## External URL which can access alertmanager + baseURL: "http://localhost:9093" + + ## Additional alertmanager container environment variable + ## For instance to add a http_proxy + ## + extraEnv: {} + + ## Additional alertmanager Secret mounts + # Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # subPath: "" + # secretName: alertmanager-secret-files + # readOnly: true + + ## ConfigMap override where fullname is {{.Release.Name}}-{{.Values.alertmanager.configMapOverrideName}} + ## Defining configMapOverrideName will cause templates/alertmanager-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configMapOverrideName: "" + + ## The name of a secret in the same kubernetes namespace which contains the Alertmanager config + ## Defining configFromSecret will cause templates/alertmanager-configmap.yaml + ## to NOT generate a ConfigMap resource + ## + configFromSecret: "" + + ## The configuration file name to be loaded to alertmanager + ## Must match the key within configuration loaded from ConfigMap/Secret + ## + configFileName: alertmanager.yml + + ingress: + ## If true, alertmanager Ingress will be created + ## + enabled: false + + ## alertmanager Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## alertmanager Ingress additional labels + ## + extraLabels: {} + + ## alertmanager Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - alertmanager.domain.com + # - domain.com/alertmanager + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## alertmanager Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-alerts-tls + # hosts: + # - alertmanager.domain.com + + ## Alertmanager Deployment Strategy type + # strategy: + # type: Recreate + + ## Node tolerations for alertmanager scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for alertmanager pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Pod affinity + ## + affinity: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + persistentVolume: + ## If true, alertmanager will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## alertmanager data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## alertmanager data Persistent Volume Claim annotations + ## + annotations: {} + + ## alertmanager data Persistent Volume existing claim name + ## Requires alertmanager.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## alertmanager data Persistent Volume mount root path + ## + mountPath: /data + + ## alertmanager data Persistent Volume size + ## + size: 2Gi + + ## alertmanager data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## alertmanager data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of alertmanager data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + ## Annotations to be added to alertmanager pods + ## + podAnnotations: {} + ## Tell prometheus to use a specific set of alertmanager pods + ## instead of all alertmanager pods found in the same namespace + ## Useful if you deploy multiple releases within the same namespace + ## + ## prometheus.io/probe: alertmanager-teamA + + ## Labels to be added to Prometheus AlertManager pods + ## + podLabels: {} + + ## Use a StatefulSet if replicaCount needs to be greater than 1 (see below) + ## + replicaCount: 1 + + statefulSet: + ## If true, use a statefulset instead of a deployment for pod management. + ## This allows to scale replicas to more than 1 pod + ## + enabled: false + + podManagementPolicy: OrderedReady + + ## Alertmanager headless service to use for the statefulset + ## + headless: + annotations: {} + labels: {} + + ## Enabling peer mesh service end points for enabling the HA alert manager + ## Ref: https://github.com/prometheus/alertmanager/blob/master/README.md + # enableMeshPeer : true + + servicePort: 80 + + ## alertmanager resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 10m + # memory: 32Mi + # requests: + # cpu: 10m + # memory: 32Mi + + ## Security context to be added to alertmanager pods + ## + securityContext: + runAsUser: 1001 + runAsNonRoot: true + runAsGroup: 1001 + fsGroup: 1001 + + service: + annotations: {} + labels: {} + clusterIP: "" + + ## Enabling peer mesh service end points for enabling the HA alert manager + ## Ref: https://github.com/prometheus/alertmanager/blob/master/README.md + # enableMeshPeer : true + + ## List of IP addresses at which the alertmanager service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 80 + # nodePort: 30000 + sessionAffinity: None + type: ClusterIP + + # Define a custom scheduler for Alertmanager pods + # schedulerName: default-scheduler + + ## alertmanager ConfigMap entries + ## + alertmanagerFiles: + alertmanager.yml: + global: {} + # slack_api_url: '' + + receivers: + - name: default-receiver + # slack_configs: + # - channel: '@you' + # send_resolved: true + + route: + group_wait: 10s + group_interval: 5m + receiver: default-receiver + repeat_interval: 3h + + ## Monitors ConfigMap changes and POSTs to a URL + configmapReload: + prometheus: + ## If false, the configmap-reload container will not be deployed + ## + enabled: false + + ## configmap-reload container name + ## + name: configmap-reload + + ## configmap-reload container image + ## + image: + repository: quay.io/prometheus-operator/prometheus-config-reloader + tag: v0.74.0 + pullPolicy: IfNotPresent + + ## Additional configmap-reload container arguments + ## + extraArgs: {} + ## Additional configmap-reload volume directories + ## + extraVolumeDirs: [] + + ## Additional configmap-reload mounts + ## + extraConfigmapMounts: [] + # - name: prometheus-alerts + # mountPath: /etc/alerts.d + # subPath: "" + # configMap: prometheus-alerts + # readOnly: true + + ## configmap-reload resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + + ## configmap-reload container securityContext + containerSecurityContext: {} + + alertmanager: + ## If false, the configmap-reload container will not be deployed + ## + enabled: false + + ## configmap-reload container name + ## + name: configmap-reload + + ## configmap-reload container image + ## + image: + repository: quay.io/prometheus-operator/prometheus-config-reloader + tag: v0.74.0 + pullPolicy: IfNotPresent + + ## Additional configmap-reload container arguments + ## + extraArgs: {} + ## Additional configmap-reload volume directories + ## + extraVolumeDirs: [] + + + ## Additional configmap-reload mounts + ## + extraConfigmapMounts: [] + # - name: prometheus-alerts + # mountPath: /etc/alerts.d + # subPath: "" + # configMap: prometheus-alerts + # readOnly: true + + + ## configmap-reload resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + + # node-export must be disabled if there is an existing daemonset: https://guide.kubecost.com/hc/en-us/articles/4407601830679-Troubleshoot-Install#a-name-node-exporter-a-issue-failedscheduling-kubecost-prometheus-node-exporter + nodeExporter: + ## If false, node-exporter will not be installed. + ## This is disabled by default in Kubecost 2.0, though it can be enabled as needed. + ## + enabled: false + + ## Provide a full name override for node exporter. + # fullnameOverride: "" + + ## If true, node-exporter pods share the host network namespace + ## + hostNetwork: true + + ## If true, node-exporter pods share the host PID namespace + ## + hostPID: true + + ## node-exporter dns policy + ## + dnsPolicy: ClusterFirstWithHostNet + + ## node-exporter container name + ## + name: node-exporter + + ## node-exporter container image + ## + image: + repository: prom/node-exporter + tag: v1.8.0 + pullPolicy: IfNotPresent + + ## node-exporter priorityClassName + ## + priorityClassName: "" + + ## Custom Update Strategy + ## + updateStrategy: + type: RollingUpdate + + ## Additional node-exporter container arguments + ## + extraArgs: {} + + ## Additional node-exporter hostPath mounts + ## + extraHostPathMounts: [] + # - name: textfile-dir + # mountPath: /srv/txt_collector + # hostPath: /var/lib/node-exporter + # readOnly: true + # mountPropagation: HostToContainer + + extraConfigmapMounts: [] + # - name: certs-configmap + # mountPath: /prometheus + # configMap: certs-configmap + # readOnly: true + + ## Set a custom affinity for node-exporter + ## + # affinity: + + ## Node tolerations for node-exporter scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for node-exporter pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Annotations to be added to node-exporter pods + ## + podAnnotations: {} + + ## Annotations to be added to the node-exporter DaemonSet + ## + deploymentAnnotations: {} + + ## Labels to be added to node-exporter pods + ## + pod: + labels: {} + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## node-exporter resource limits & requests + ## Ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 200m + # memory: 50Mi + # requests: + # cpu: 100m + # memory: 30Mi + + ## Security context to be added to node-exporter pods + ## + securityContext: {} + # runAsUser: 0 + + service: + annotations: + prometheus.io/scrape: "true" + labels: {} + + # Exposed as a headless service: + # https://kubernetes.io/docs/concepts/services-networking/service/#headless-services + clusterIP: None + + ## List of IP addresses at which the node-exporter service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + hostPort: 9100 + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 9100 + type: ClusterIP + + # Install Prometheus Push Gateway. + pushgateway: + ## If false, pushgateway will not be installed + ## + enabled: false + + ## Provide a full name override for Prometheus push gateway. + # fullnameOverride: "" + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## pushgateway container name + ## + name: pushgateway + + ## pushgateway container image + ## + image: + repository: prom/pushgateway + tag: v1.8.0 + pullPolicy: IfNotPresent + + ## pushgateway priorityClassName + ## + priorityClassName: "" + + ## Additional pushgateway container arguments + ## + ## for example: persistence.file: /data/pushgateway.data + extraArgs: {} + + ingress: + ## If true, pushgateway Ingress will be created + ## + enabled: false + + ## pushgateway Ingress annotations + ## + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: 'true' + + ## pushgateway Ingress hostnames with optional path + ## Must be provided if Ingress is enabled + ## + hosts: [] + # - pushgateway.domain.com + # - domain.com/pushgateway + + ## Extra paths to prepend to every host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # serviceName: ssl-redirect + # servicePort: use-annotation + + ## pushgateway Ingress TLS configuration + ## Secrets must be manually created in the namespace + ## + tls: [] + # - secretName: prometheus-alerts-tls + # hosts: + # - pushgateway.domain.com + + ## Node tolerations for pushgateway scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + ## Node labels for pushgateway pod assignment + ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Annotations to be added to pushgateway pods + ## + podAnnotations: {} + + replicaCount: 1 + + ## PodDisruptionBudget settings + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ + ## + podDisruptionBudget: + enabled: false + maxUnavailable: 1 + + ## pushgateway resource requests and limits + ## Ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + # limits: + # cpu: 10m + # memory: 32Mi + # requests: + # cpu: 10m + # memory: 32Mi + + ## Security context to be added to push-gateway pods + ## + securityContext: + runAsUser: 1001 + runAsNonRoot: true + + service: + annotations: + prometheus.io/probe: pushgateway + labels: {} + clusterIP: "" + + ## List of IP addresses at which the pushgateway service is available + ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips + ## + externalIPs: [] + + loadBalancerIP: "" + loadBalancerSourceRanges: [] + servicePort: 9091 + type: ClusterIP + + strategy: + type: Recreate + rollingUpdate: null + + + persistentVolume: + ## If true, pushgateway will create/use a Persistent Volume Claim + ## If false, use emptyDir + ## + enabled: true + + ## pushgateway data Persistent Volume access modes + ## Must match those of existing PV or dynamic provisioner + ## Ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + accessModes: + - ReadWriteOnce + + ## pushgateway data Persistent Volume Claim annotations + ## + annotations: {} + + ## pushgateway data Persistent Volume existing claim name + ## Requires pushgateway.persistentVolume.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: "" + + ## pushgateway data Persistent Volume mount root path + ## + mountPath: /data + + ## pushgateway data Persistent Volume size + ## + size: 2Gi + + ## pushgateway data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + + ## pushgateway data Persistent Volume Binding Mode + ## If defined, volumeBindingMode: + ## If undefined (the default) or set to null, no volumeBindingMode spec is + ## set, choosing the default mode. + ## + # volumeBindingMode: "" + + ## Subdirectory of pushgateway data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + + serverFiles: + ## Alerts configuration + ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + alerting_rules.yml: {} + # groups: + # - name: Instances + # rules: + # - alert: InstanceDown + # expr: up == 0 + # for: 5m + # labels: + # severity: page + # annotations: + # description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes.' + # summary: 'Instance {{ $labels.instance }} down' + ## DEPRECATED DEFAULT VALUE, unless explicitly naming your files, please use alerting_rules.yml + alerts: {} + + ## Records configuration + ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/ + recording_rules.yml: {} + ## DEPRECATED DEFAULT VALUE, unless explicitly naming your files, please use recording_rules.yml + + prometheus.yml: + rule_files: + - /etc/config/recording_rules.yml + - /etc/config/alerting_rules.yml + ## Below two files are DEPRECATED will be removed from this default values file + - /etc/config/rules + - /etc/config/alerts + + scrape_configs: + - job_name: prometheus + static_configs: + - targets: + - localhost:9090 + + # A scrape configuration for running Prometheus on a Kubernetes cluster. + # This uses separate scrape configs for cluster components (i.e. API server, node) + # and services to allow each to use different authentication configs. + # + # Kubernetes labels will be added as Prometheus labels on metrics via the + # `labelmap` relabeling action. + + - job_name: 'kubernetes-nodes-cadvisor' + + # Default to scraping over https. If required, just disable this or change to + # `http`. + scheme: https + + # This TLS & bearer token file config is used to connect to the actual scrape + # endpoints for cluster components. This is separate to discovery auth + # configuration because discovery & scraping are two separate concerns in + # Prometheus. The discovery auth config is automatic if Prometheus runs inside + # the cluster. Otherwise, more config options have to be provided within the + # . + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + # If your node certificates are self-signed or use a different CA to the + # master CA, then disable certificate verification below. Note that + # certificate verification is an integral part of a secure infrastructure + # so this should only be disabled in a controlled environment. You can + # disable certificate verification by uncommenting the line below. + # + insecure_skip_verify: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + + kubernetes_sd_configs: + - role: node + + # This configuration will work only on kubelet 1.7.3+ + # As the scrape endpoints for cAdvisor have changed + # if you are using older version you need to change the replacement to + # replacement: /api/v1/nodes/$1:4194/proxy/metrics + # more info here https://github.com/coreos/prometheus-operator/issues/633 + relabel_configs: + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: [__meta_kubernetes_node_name] + regex: (.+) + target_label: __metrics_path__ + replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor + + metric_relabel_configs: + - source_labels: [__name__] + regex: (container_cpu_usage_seconds_total|container_memory_working_set_bytes|container_network_receive_errors_total|container_network_transmit_errors_total|container_network_receive_packets_dropped_total|container_network_transmit_packets_dropped_total|container_memory_usage_bytes|container_cpu_cfs_throttled_periods_total|container_cpu_cfs_periods_total|container_fs_usage_bytes|container_fs_limit_bytes|container_cpu_cfs_periods_total|container_fs_inodes_free|container_fs_inodes_total|container_fs_usage_bytes|container_fs_limit_bytes|container_cpu_cfs_throttled_periods_total|container_cpu_cfs_periods_total|container_network_receive_bytes_total|container_network_transmit_bytes_total|container_fs_inodes_free|container_fs_inodes_total|container_fs_usage_bytes|container_fs_limit_bytes|container_spec_cpu_shares|container_spec_memory_limit_bytes|container_network_receive_bytes_total|container_network_transmit_bytes_total|container_fs_reads_bytes_total|container_network_receive_bytes_total|container_fs_writes_bytes_total|container_fs_reads_bytes_total|cadvisor_version_info|kubecost_pv_info) + action: keep + - source_labels: [container] + target_label: container_name + regex: (.+) + action: replace + - source_labels: [pod] + target_label: pod_name + regex: (.+) + action: replace + + # A scrape configuration for running Prometheus on a Kubernetes cluster. + # This uses separate scrape configs for cluster components (i.e. API server, node) + # and services to allow each to use different authentication configs. + # + # Kubernetes labels will be added as Prometheus labels on metrics via the + # `labelmap` relabeling action. + + - job_name: 'kubernetes-nodes' + + # Default to scraping over https. If required, just disable this or change to + # `http`. + scheme: https + + # This TLS & bearer token file config is used to connect to the actual scrape + # endpoints for cluster components. This is separate to discovery auth + # configuration because discovery & scraping are two separate concerns in + # Prometheus. The discovery auth config is automatic if Prometheus runs inside + # the cluster. Otherwise, more config options have to be provided within the + # . + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + # If your node certificates are self-signed or use a different CA to the + # master CA, then disable certificate verification below. Note that + # certificate verification is an integral part of a secure infrastructure + # so this should only be disabled in a controlled environment. You can + # disable certificate verification by uncommenting the line below. + # + insecure_skip_verify: true + bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + + kubernetes_sd_configs: + - role: node + + relabel_configs: + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: [__meta_kubernetes_node_name] + regex: (.+) + target_label: __metrics_path__ + replacement: /api/v1/nodes/$1/proxy/metrics + + metric_relabel_configs: + - source_labels: [__name__] + regex: (kubelet_volume_stats_used_bytes) # this metric is in alpha + action: keep + + # Scrape config for service endpoints. + # + # The relabeling allows the actual service scrape endpoint to be configured + # via the following annotations: + # + # * `prometheus.io/scrape`: Only scrape services that have a value of `true` + # * `prometheus.io/scheme`: If the metrics endpoint is secured then you will need + # to set this to `https` & most likely set the `tls_config` of the scrape config. + # * `prometheus.io/path`: If the metrics path is not `/metrics` override this. + # * `prometheus.io/port`: If the metrics are exposed on a different port to the + # service then set this appropriately. + - job_name: 'kubernetes-service-endpoints' + + kubernetes_sd_configs: + - role: endpoints + + relabel_configs: + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] + action: keep + regex: true + - source_labels: [__meta_kubernetes_endpoints_name] + action: keep + regex: (.*node-exporter|kubecost-network-costs) + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] + action: replace + target_label: __scheme__ + regex: (https?) + - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] + action: replace + target_label: __address__ + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + - action: labelmap + regex: __meta_kubernetes_service_label_(.+) + - source_labels: [__meta_kubernetes_namespace] + action: replace + target_label: kubernetes_namespace + - source_labels: [__meta_kubernetes_service_name] + action: replace + target_label: kubernetes_name + - source_labels: [__meta_kubernetes_pod_node_name] + action: replace + target_label: kubernetes_node + metric_relabel_configs: + - source_labels: [__name__] + regex: (container_cpu_allocation|container_cpu_usage_seconds_total|container_fs_limit_bytes|container_fs_writes_bytes_total|container_gpu_allocation|container_memory_allocation_bytes|container_memory_usage_bytes|container_memory_working_set_bytes|container_network_receive_bytes_total|container_network_transmit_bytes_total|DCGM_FI_DEV_GPU_UTIL|deployment_match_labels|kube_daemonset_status_desired_number_scheduled|kube_daemonset_status_number_ready|kube_deployment_spec_replicas|kube_deployment_status_replicas|kube_deployment_status_replicas_available|kube_job_status_failed|kube_namespace_annotations|kube_namespace_labels|kube_node_info|kube_node_labels|kube_node_status_allocatable|kube_node_status_allocatable_cpu_cores|kube_node_status_allocatable_memory_bytes|kube_node_status_capacity|kube_node_status_capacity_cpu_cores|kube_node_status_capacity_memory_bytes|kube_node_status_condition|kube_persistentvolume_capacity_bytes|kube_persistentvolume_status_phase|kube_persistentvolumeclaim_info|kube_persistentvolumeclaim_resource_requests_storage_bytes|kube_pod_container_info|kube_pod_container_resource_limits|kube_pod_container_resource_limits_cpu_cores|kube_pod_container_resource_limits_memory_bytes|kube_pod_container_resource_requests|kube_pod_container_resource_requests_cpu_cores|kube_pod_container_resource_requests_memory_bytes|kube_pod_container_status_restarts_total|kube_pod_container_status_running|kube_pod_container_status_terminated_reason|kube_pod_labels|kube_pod_owner|kube_pod_status_phase|kube_replicaset_owner|kube_statefulset_replicas|kube_statefulset_status_replicas|kubecost_cluster_info|kubecost_cluster_management_cost|kubecost_cluster_memory_working_set_bytes|kubecost_load_balancer_cost|kubecost_network_internet_egress_cost|kubecost_network_region_egress_cost|kubecost_network_zone_egress_cost|kubecost_node_is_spot|kubecost_pod_network_egress_bytes_total|node_cpu_hourly_cost|node_cpu_seconds_total|node_disk_reads_completed|node_disk_reads_completed_total|node_disk_writes_completed|node_disk_writes_completed_total|node_filesystem_device_error|node_gpu_count|node_gpu_hourly_cost|node_memory_Buffers_bytes|node_memory_Cached_bytes|node_memory_MemAvailable_bytes|node_memory_MemFree_bytes|node_memory_MemTotal_bytes|node_network_transmit_bytes_total|node_ram_hourly_cost|node_total_hourly_cost|pod_pvc_allocation|pv_hourly_cost|service_selector_labels|statefulSet_match_labels|kubecost_pv_info|up) + action: keep + + + # prometheus.yml: # Sample block -- enable if using an in cluster durable store. + # remote_write: + # - url: "http://pgprometheus-adapter:9201/write" + # write_relabel_configs: + # - source_labels: [__name__] + # regex: 'container_.*_allocation|container_.*_allocation_bytes|.*_hourly_cost|kube_pod_container_resource_requests{resource="memory", unit="byte"}|container_memory_working_set_bytes|kube_pod_container_resource_requests{resource="cpu", unit="core"}|kube_pod_container_resource_requests|pod_pvc_allocation|kube_namespace_labels|kube_pod_labels' + # action: keep + # queue_config: + # max_samples_per_send: 1000 + # remote_read: + # - url: "http://pgprometheus-adapter:9201/read" + rules: + groups: + - name: CPU + rules: + - expr: sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) + record: cluster:cpu_usage:rate5m + - expr: rate(container_cpu_usage_seconds_total{container!=""}[5m]) + record: cluster:cpu_usage_nosum:rate5m + - expr: avg(irate(container_cpu_usage_seconds_total{container!="POD", container!=""}[5m])) by (container,pod,namespace) + record: kubecost_container_cpu_usage_irate + - expr: sum(container_memory_working_set_bytes{container!="POD",container!=""}) by (container,pod,namespace) + record: kubecost_container_memory_working_set_bytes + - expr: sum(container_memory_working_set_bytes{container!="POD",container!=""}) + record: kubecost_cluster_memory_working_set_bytes + - name: Savings + rules: + - expr: sum(avg(kube_pod_owner{owner_kind!="DaemonSet"}) by (pod) * sum(container_cpu_allocation) by (pod)) + record: kubecost_savings_cpu_allocation + labels: + daemonset: "false" + - expr: sum(avg(kube_pod_owner{owner_kind="DaemonSet"}) by (pod) * sum(container_cpu_allocation) by (pod)) / sum(kube_node_info) + record: kubecost_savings_cpu_allocation + labels: + daemonset: "true" + - expr: sum(avg(kube_pod_owner{owner_kind!="DaemonSet"}) by (pod) * sum(container_memory_allocation_bytes) by (pod)) + record: kubecost_savings_memory_allocation_bytes + labels: + daemonset: "false" + - expr: sum(avg(kube_pod_owner{owner_kind="DaemonSet"}) by (pod) * sum(container_memory_allocation_bytes) by (pod)) / sum(kube_node_info) + record: kubecost_savings_memory_allocation_bytes + labels: + daemonset: "true" + + # Adds option to add alert_relabel_configs to avoid duplicate alerts in alertmanager + # useful in H/A prometheus with different external labels but the same alerts + alertRelabelConfigs: + # alert_relabel_configs: + # - source_labels: [dc] + # regex: (.+)\d+ + # target_label: dc + + networkPolicy: + ## Enable creation of NetworkPolicy resources. + ## + enabled: false + + +## Optional daemonset to more accurately attribute network costs to the correct workload +## https://docs.kubecost.com/install-and-configure/advanced-configuration/network-costs-configuration +networkCosts: + enabled: false + image: + repository: gcr.io/kubecost1/kubecost-network-costs + tag: v0.17.3 + imagePullPolicy: IfNotPresent + updateStrategy: + type: RollingUpdate + # For existing Prometheus Installs, use the serviceMonitor: or prometheusScrape below. + # the below setting annotates the networkCost service endpoints for each of the network-costs pods. + # The Service is annotated with prometheus.io/scrape: "true" to automatically get picked up by the prometheus config. + # NOTE: Setting this option to true and leaving the above extraScrapeConfig "job_name: kubecost-networking" configured will cause the + # NOTE: pods to be scraped twice. + prometheusScrape: false + # Traffic Logging will enable logging the top 5 destinations for each source + # every 30 minutes. + trafficLogging: true + + logLevel: info + + # Port will set both the containerPort and hostPort to this value. + # These must be identical due to network-costs being run on hostNetwork + port: 3001 + # this daemonset can use significant resources on large clusters: https://guide.kubecost.com/hc/en-us/articles/4407595973527-Network-Traffic-Cost-Allocation + resources: + limits: # remove the limits by setting cpu: null + cpu: 500m # can be less, will depend on cluster size + # memory: it is not recommended to set a memory limit + requests: + cpu: 50m + memory: 20Mi + extraArgs: [] + config: + # Configuration for traffic destinations, including specific classification + # for IPs and CIDR blocks. This configuration will act as an override to the + # automatic classification provided by network-costs. + destinations: + # In Zone contains a list of address/range that will be + # classified as in zone. + in-zone: + # Loopback Addresses in "IANA IPv4 Special-Purpose Address Registry" + - "127.0.0.0/8" + # IPv4 Link Local Address Space + - "169.254.0.0/16" + # Private Address Ranges in RFC-1918 + - "10.0.0.0/8" # Remove this entry if using Multi-AZ Kubernetes + - "172.16.0.0/12" + - "192.168.0.0/16" + + # In Region contains a list of address/range that will be + # classified as in region. This is synonymous with cross + # zone traffic, where the regions between source and destinations + # are the same, but the zone is different. + in-region: [] + + # Cross Region contains a list of address/range that will be + # classified as non-internet egress from one region to another. + cross-region: [] + + # Internet contains a list of address/range that will be + # classified as internet traffic. This is synonymous with traffic + # that cannot be classified within the cluster. + # NOTE: Internet classification filters are executed _after_ + # NOTE: direct-classification, but before in-zone, in-region, + # NOTE: and cross-region. + internet: [] + + # Direct Classification specifically maps an ip address or range + # to a region (required) and/or zone (optional). This classification + # takes priority over in-zone, in-region, and cross-region configurations. + direct-classification: [] + # - region: "us-east1" + # zone: "us-east1-c" + # ips: + # - "10.0.0.0/24" + services: + # google-cloud-services: when set to true, enables labeling traffic metrics with google cloud + # service endpoints + google-cloud-services: true + # amazon-web-services: when set to true, enables labeling traffic metrics with amazon web service + # endpoints. + amazon-web-services: true + # azure-cloud-services: when set to true, enables labeling traffic metrics with azure cloud service + # endpoints + azure-cloud-services: true + # user defined services provide a way to define custom service endpoints which will label traffic metrics + # falling within the defined address range. + # services: + # - service: "test-service-1" + # ips: + # - "19.1.1.2" + # - service: "test-service-2" + # ips: + # - "15.128.15.2" + # - "20.0.0.0/8" + + ## Node tolerations for server scheduling to nodes with taints + ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal|Exists" + # value: "value" + # effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)" + + affinity: {} + + service: + annotations: {} + labels: {} + + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + ## PodMonitor + ## Allows scraping of network metrics from a dedicated prometheus operator setup + podMonitor: + enabled: false + additionalLabels: {} + # match the default extraScrapeConfig + additionalLabels: {} + nodeSelector: {} + annotations: {} + healthCheckProbes: {} + # readinessProbe: + # tcpSocket: + # port: 3001 + # initialDelaySeconds: 5 + # periodSeconds: 10 + # failureThreshold: 5 + # livenessProbe: + # tcpSocket: + # port: 3001 + # initialDelaySeconds: 5 + # periodSeconds: 10 + # failureThreshold: 5 + additionalSecurityContext: {} + # readOnlyRootFilesystem: true + +## Kubecost Deployment Configuration +## Used for HA mode in Business & Enterprise tier +## +kubecostDeployment: + replicas: 1 + # deploymentStrategy: + # rollingUpdate: + # maxSurge: 1 + # maxUnavailable: 1 + # type: RollingUpdate + labels: {} + annotations: {} + + +## Kubecost Forecasting forecasts future cost patterns based on historical +## patterns observed by Kubecost. +forecasting: + enabled: true + + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for the forecasting + # container. + # Example: fullImageName: gcr.io/kubecost1/forecasting:v0.0.1 + fullImageName: gcr.io/kubecost1/kubecost-modeling:v0.1.15 + imagePullPolicy: IfNotPresent + + # Resource specification block for the forecasting container. + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + cpu: 1500m + memory: 1Gi + + # Set environment variables for the forecasting container as key/value pairs. + env: + # -t is the worker timeout which primarily affects model training time; + # if it is not high enough, training workers may die mid training + "GUNICORN_CMD_ARGS": "--log-level info -t 1200" + + # Define a priority class for the forecasting Deployment. + priority: + enabled: false + name: "" + + # Define a nodeSelector for the forecasting Deployment. + nodeSelector: {} + + # Define tolerations for the forecasting Deployment. + tolerations: [] + + # Define Pod affinity for the forecasting Deployment. + affinity: {} + + # Define a readiness probe for the forecasting container + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + # Define a liveness probe for the forecasting container. + livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + +## The Kubecost Aggregator is a high scale implementation of Kubecost intended +## for large datasets and/or high query load. At present, this should only be +## enabled when recommended by Kubecost staff. +## +kubecostAggregator: + # deployMethod determines how Aggregator is deployed. Current options are + # "singlepod" (within cost-analyzer Pod) "statefulset" (separate + # StatefulSet), and "disabled". Only use "disabled" if this is a secondary + # Federated ETL cluster which does not need to answer queries. + deployMethod: singlepod + + # fullImageName overrides the default image construction logic. The exact + # image provided (registry, image, tag) will be used for aggregator. + # fullImageName: + imagePullPolicy: IfNotPresent + + # For legacy configuration support, `enabled: true` overrides deployMethod + # and causes `deployMethod: "statefulset"` + enabled: false + + # the below settings should only be modified with support from Kubecost staff + # Replicas sets the number of Aggregator replicas. It only has an effect if + # `deployMethod: "statefulset"` + replicas: 1 + + # stagingEmptyDirSizeLimit changes how large the "staging" + # /var/configs/waterfowl emptyDir is. It only takes effect in StatefulSet + # configurations of Aggregator, other configurations are unaffected. + # + # It should be set to approximately 8x the size of the largest bingen file in + # object storage. For example, if your largest bingen file is a daily + # Allocation file with size 300MiB, this value should be set to approximately + # 2400Mi. In most environments, the default should suffice. + stagingEmptyDirSizeLimit: 2Gi + + # this is the number of partitions the datastore is split into for copying + # the higher this number, the lower the ram usage but the longer it takes for + # new data to show in the kubecost UI + # set to 0 for max partitioning (minimum possible ram usage, but the slowest) + # the default of 25 is sufficient for 95%+ of users. This should only be modified + # after consulting with Kubecost's support team + numDBCopyPartitions: 25 + + # env: has been removed to avoid unknown issues that would be caused by + # customizations that were required to run aggregator in previous versions + # extraEnv: can be used to add new environment variables to the aggregator pod + + logLevel: info + + # How many threads the read database is configured with (i.e. Kubecost API / + # UI queries). If increasing this value, it is recommended to increase the + # aggregator's memory requests & limits. + # default: 1 + dbReadThreads: 1 + # How many threads the write database is configured with (i.e. ingestion of + # new data from S3). If increasing this value, it is recommended to increase + # the aggregator's memory requests & limits. + # default: 1 + dbWriteThreads: 1 + # How many threads to use when ingesting Asset/Allocation/CloudCost data + # from the federated store bucket. In most cases the default is sufficient, + # but can be increased if trying to backfill historical data. + # default: 1 + dbConcurrentIngestionCount: 1 + + # Memory limit applied to read database and write database connections. The + # default of "no limit" is appropriate when first establishing a baseline of + # resource usage required. It is eventually recommended to set these values + # such that dbMemoryLimit + dbWriteMemoryLimit < the total memory available + # to the aggregator pod. + # default: 0GB is no limit + dbMemoryLimit: 0GB + # Memory limit applied to write database connections. + # default: 0GB is no limit + dbWriteMemoryLimit: 0GB + # How much data to ingest from the federated store bucket, and how much data + # to keep in the DB before rolling the data off. + # + # Note: If increasing this value to backfill historical data, it will take + # time to gradually ingest and process those historical ETL files. Consider + # also increasing the resources available to the aggregator as well as the + # refresh and concurrency env vars. + # + # default: 91 + etlDailyStoreDurationDays: 91 + # Trim memory on close, only change if advised by Kubecost support. + dbTrimMemoryOnClose: true + + persistentConfigsStorage: + storageClass: "" # default storage class + storageRequest: 1Gi + aggregatorDbStorage: + storageClass: "" # default storage class + storageRequest: 128Gi + + resources: {} + # requests: + # cpu: 1000m + # memory: 1Gi + + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + ## Set additional environment variables for the aggregator pod + # extraEnv: + # - name: SOME_VARIABLE + # value: "some_value" + + ## Add a priority class to the aggregator pod + # priority: + # enabled: false + # name: "" + + ## Optional - add extra ports to the aggregator container. For kubecost development purposes only - not recommended for users. + # extraPorts: [] + # - name: debug + # port: 40000 + # targetPort: 40000 + # containerPort: 40000 + + ## Define a securityContext for the aggregator pod. This will take highest precedence. + # securityContext: {} + + ## Define the container-level security context for the aggregator pod. This will take highest precedence. + # containerSecurityContext: {} + + ## Provide a Service Account name for aggregator. + # serviceAccountName: "" + + ## Define a nodeSelector for the aggregator pod + # nodeSelector: {} + + ## Define tolerations for the aggregator pod + # tolerations: [] + + ## Define Pod affinity for the aggregator pod + # affinity: {} + + ## Define extra volumes for the aggregator pod + # extraVolumes: [] + + ## Define extra volumemounts for the aggregator pod + # extraVolumeMounts: [] + + ## Creates a new container/pod to retrieve CloudCost data. By default it uses + ## the same serviceaccount as the cost-analyzer pod. A custom serviceaccount + ## can be specified. + cloudCost: + # The cloudCost component of Aggregator depends on + # kubecostAggregator.deployMethod: + # kA.dM = "singlepod" -> cloudCost is run as container inside cost-analyzer + # kA.dM = "statefulset" -> cloudCost is run as single-replica Deployment + resources: {} + # requests: + # cpu: 1000m + # memory: 1Gi + # refreshRateHours: + # queryWindowDays: + # runWindowDays: + # serviceAccountName: + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + failureThreshold: 200 + + ## Add a nodeSelector for aggregator cloud costs + # nodeSelector: {} + + ## Tolerations for the aggregator cloud costs + # tolerations: [] + + ## Affinity for the aggregator cloud costs + # affinity: {} + + ## ServiceAccount for the aggregator cloud costs + # serviceAccountName: "" + + ## Define environment variables for cloud cost + # env: {} + + ## Define extra volumes for the cloud cost pod + # extraVolumes: [] + + ## Define extra volumemounts for the cloud cost pod + # extraVolumeMounts: [] + + ## Configure the Collections service for aggregator. + # collections: + # cache: + # enabled: false + + # Jaeger is an optional container attached to wherever the Aggregator + # container is running. It is used for performance investigation. Enable if + # Kubecost Support asks. + jaeger: + enabled: false + image: jaegertracing/all-in-one + imageVersion: latest + # containerSecurityContext: + +## Kubecost Multi-cluster Diagnostics (beta) +## A single view into the health of all agent clusters. Each agent cluster sends +## its diagnostic data to a storage bucket. Future versions may include +## repairing & alerting from the primary. +## Ref: https://docs.kubecost.com/install-and-configure/install/multi-cluster-diagnostics +## +diagnostics: + enabled: true + + ## The primary aggregates all diagnostic data and handles API requests. It's + ## also responsible for deleting diagnostic data (on disk & bucket) beyond + ## retention. When in readonly mode it does not push its own diagnostic data + ## to the bucket. + primary: + enabled: false + retention: "7d" + readonly: false + + ## How frequently to run & push diagnostics. Defaults to 5 minutes. + pollingInterval: "300s" + + ## Creates a new Diagnostic file in the bucket for every run. + keepDiagnosticHistory: false + + ## Pushes the cluster's Kubecost Helm Values to the bucket once upon startup. + ## This may contain sensitive information and is roughly 30kb per cluster. + collectHelmValues: false + + ## By default, the Multi-cluster Diagnostics service runs within the + ## cost-model container in the cost-analyzer pod. For higher availability, it + ## can be run as a separate deployment. + deployment: + enabled: false + resources: + requests: + cpu: "10m" + memory: "20Mi" + env: {} + labels: {} + securityContext: {} + containerSecurityContext: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +## Provide a full name override for the diagnostics Deployment. +# diagnosticsFullnameOverride: "" + +# Kubecost Cluster Controller for Right Sizing and Cluster Turndown +clusterController: + enabled: false + image: + repository: gcr.io/kubecost1/cluster-controller + tag: v0.16.8 + imagePullPolicy: IfNotPresent + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + # Set custom tolerations for the cluster controller. + tolerations: [] + actionConfigs: + # this configures the Kubecost Cluster Turndown action + # for more details, see documentation at https://github.com/kubecost/cluster-turndown/tree/develop?tab=readme-ov-file#setting-a-turndown-schedule + clusterTurndown: [] + # - name: my-schedule + # start: "2024-02-09T00:00:00Z" + # end: "2024-02-09T12:00:00Z" + # repeat: daily + # - name: my-schedule2 + # start: "2024-02-09T00:00:00Z" + # end: "2024-02-09T01:00:00Z" + # repeat: weekly + # this configures the Kubecost Namespace Turndown action + # for more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#namespace-turndown + namespaceTurndown: + # - name: my-ns-turndown-action + # dryRun: false + # schedule: "0 0 * * *" + # type: Scheduled + # targetObjs: + # - namespace + # keepPatterns: + # - ignorednamespace + # keepLabels: + # turndown: ignore + # params: + # minNamespaceAge: 4h + # this configures the Kubecost Cluster Sizing action + # for more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#cluster-sizing + clusterRightsize: + # startTime: '2024-01-02T15:04:05Z' + # frequencyMinutes: 1440 + # lastCompleted: '' + # recommendationParams: + # window: 48h + # architecture: '' + # targetUtilization: 0.8 + # minNodeCount: 1 + # allowSharedCore: false + # allowCostIncrease: false + # recommendationType: '' + # This configures the Kubecost Continuous Request Sizing Action + # + # Using this configuration overrides annotation-based configuration of + # Continuous Request Sizing. Annotation configuration will be ignored while + # this configuration method is present in the cluster. + # + # For more details, see documentation at https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/savings/savings-actions#automated-request-sizing + containerRightsize: + # Workloads can be selected by an _exact_ key (namespace, controllerKind, + # controllerName). This will only match a single controller. The cluster + # ID is current irrelevant because Cluster Controller can only modify + # workloads within the cluster it is running in. + # workloads: + # - clusterID: cluster-one + # namespace: my-namespace + # controllerKind: deployment + # controllerName: my-controller + # An alternative to exact key selection is filter selection. The filters + # are syntactically identical to Kubecost's "v2" filters [1] but only + # support a small set of filter fields, those being: + # - namespace + # - controllerKind + # - controllerName + # - label + # - annotation + # + # If multiple filters are listed, they will be ORed together at the top + # level. + # + # See the examples below. + # + # [1] https://docs.kubecost.com/apis/filters-api + # filterConfig: + # - filter: | + # namespace:"abc"+controllerKind:"deployment" + # - filter: | + # controllerName:"abc123"+controllerKind:"daemonset" + # - filter: | + # namespace:"foo"+controllerKind!:"statefulset" + # - filter: | + # namespace:"bar","baz" + # schedule: + # start: "2024-01-30T15:04:05Z" + # frequencyMinutes: 5 + # recommendationQueryWindow: "48h" + # lastModified: '' + # targetUtilizationCPU: 0.8 # results in a cpu request setting that is 20% higher than the max seen over last 48h + # targetUtilizationMemory: 0.8 # results in a RAM request setting that is 20% higher than the max seen over last 48h + + kubescaler: + # If true, will cause all (supported) workloads to be have their requests + # automatically right-sized on a regular basis. + defaultResizeAll: false +# fqdn: kubecost-cluster-controller.kubecost.svc.cluster.local:9731 + namespaceTurndown: + rbac: + enabled: true + +reporting: + # Kubecost bug report feature: Logs access/collection limited to .Release.Namespace + # Ref: http://docs.kubecost.com/bug-report + logCollection: true + # Basic frontend analytics + productAnalytics: true + + # Report Javascript errors + errorReporting: true + valuesReporting: true + # googleAnalyticsTag allows you to embed your Google Global Site Tag to track usage of Kubecost. + # googleAnalyticsTag is only included in our Enterprise offering. + # googleAnalyticsTag: G-XXXXXXXXX + +serviceMonitor: # the kubecost included prometheus uses scrapeConfigs and does not support service monitors. The following options assume an existing prometheus that supports serviceMonitors. + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + networkCosts: + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: [] + aggregatorMetrics: + enabled: false + interval: 1m + scrapeTimeout: 10s + additionalLabels: {} + metricRelabelings: [] + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_namespace + targetLabel: namespace +prometheusRule: + enabled: false + additionalLabels: {} + +supportNFS: false +# initChownDataImage ensures all Kubecost filepath permissions on PV or local storage are set up correctly. +initChownDataImage: "busybox" # Supports a fully qualified Docker image, e.g. registry.hub.docker.com/library/busybox:latest +initChownData: + resources: {} + # requests: + # cpu: "50m" + # memory: "20Mi" + +grafana: + # namespace_datasources: kubecost # override the default namespace here + # namespace_dashboards: kubecost # override the default namespace here + rbac: + create: true + + serviceAccount: + create: true + name: "" + + ## Provide a full name override for the Grafana Deployment. + # fullnameOverride: "" + ## Provide a name override for the Grafana Deployment. + # nameOverride: "" + + ## Configure grafana datasources + ## ref: http://docs.grafana.org/administration/provisioning/#datasources + ## + # datasources: + # datasources.yaml: + # apiVersion: 1 + # datasources: + # - name: prometheus-kubecost + # type: prometheus + # url: http://kubecost-prometheus-server.kubecost.svc.cluster.local + # access: proxy + # isDefault: false + # jsonData: + # httpMethod: POST + # prometheusType: Prometheus + # prometheusVersion: 2.35.0 + # timeInterval: 1m + + ## Number of replicas for the Grafana deployment + replicas: 1 + + ## Deployment strategy for the Grafana deployment + deploymentStrategy: RollingUpdate + + ## Readiness probe for the Grafana deployment + readinessProbe: + httpGet: + path: /api/health + port: 3000 + + ## Liveness probe for the Grafana deployment + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 60 + timeoutSeconds: 30 + failureThreshold: 10 + + ## Container image settings for the Grafana deployment + image: + repository: grafana/grafana + tag: 11.1.4 + pullPolicy: IfNotPresent + + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + # pullSecrets: + # - myRegistrKeySecretName + + ## Pod-level security context for the Grafana deployment. Recommended let global defaults take effect. + securityContext: {} + # runAsUser: 472 + # fsGroup: 472 + + ## PriorityClassName for the Grafana deployment + priorityClassName: "" + + ## Container image settings for Grafana initContainer used to download dashboards. Will only be used when dashboards are present. + downloadDashboardsImage: + repository: curlimages/curl + tag: latest + pullPolicy: IfNotPresent + + ## Pod Annotations for the Grafana deployment + podAnnotations: {} + + ## Deployment annotations for the Grafana deployment + annotations: {} + + ## Expose the Grafana service to be accessed from outside the cluster (LoadBalancer service). + ## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it. + service: + type: ClusterIP + port: 80 + annotations: {} + labels: {} + + ## Ingress service for the Grafana deployment + ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + labels: {} + path: / + pathType: Prefix + hosts: + - chart-example.local + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + ## Resource requests and limits for the Grafana deployment + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + ## Node labels for pod assignment of the Grafana deployment + nodeSelector: {} + + ## Tolerations for pod assignment of the Grafana deployment + tolerations: [] + + ## Affinity for pod assignment of the Grafana deployment + affinity: {} + + ## Enable persistence using Persistent Volume Claims of the Grafana deployment + persistence: + enabled: false + # storageClassName: default + # accessModes: + # - ReadWriteOnce + # size: 10Gi + # annotations: {} + # subPath: "" + # existingClaim: + + ## Admin user for Grafana + adminUser: admin + + ## Admin password for Grafana + adminPassword: strongpassword + + ## Use an alternate scheduler for the Grafana deployment + # schedulerName: + + ## Extra environment variables that will be passed onto Grafana deployment pods + env: {} + + ## The name of a secret for Grafana in the same Kubernetes namespace which contain values to be added to the environment + ## This can be useful for auth tokens, etc + envFromSecret: "" + + ## Additional Grafana server secret mounts + ## Defines additional mounts with secrets. Secrets must be manually created in the namespace. + extraSecretMounts: [] + # - name: secret-files + # mountPath: /etc/secrets + # secretName: grafana-secret-files + # readOnly: true + + ## List of Grafana plugins + plugins: [] + # - digrich-bubblechart-panel + # - grafana-clock-panel + + ## Grafana dashboard providers + ## ref: http://docs.grafana.org/administration/provisioning/#dashboards + ## + ## `path` must be /var/lib/grafana/dashboards/ + ## + dashboardProviders: {} + # dashboardproviders.yaml: + # apiVersion: 1 + # providers: + # - name: 'default' + # orgId: 1 + # folder: '' + # type: file + # disableDeletion: false + # editable: true + # options: + # path: /var/lib/grafana/dashboards/default + + ## Configure Grafana dashboard to import + ## NOTE: To use dashboards you must also enable/configure dashboardProviders + ## ref: https://grafana.com/dashboards + ## + ## dashboards per provider, use provider name as key. + ## + dashboards: {} + # default: + # prometheus-stats: + # gnetId: 3662 + # revision: 2 + # datasource: Prometheus + + ## Reference to external Grafana ConfigMap per provider. Use provider name as key and ConfiMap name as value. + ## A provider dashboards must be defined either by external ConfigMaps or in values.yaml, not in both. + ## ConfigMap data example: + ## + ## data: + ## example-dashboard.json: | + ## RAW_JSON + ## + dashboardsConfigMaps: {} + # default: "" + + ## LDAP Authentication for Grafana can be enabled with the following values on grafana.ini + ## NOTE: Grafana will fail to start if the value for ldap.toml is invalid + # auth.ldap: + # enabled: true + # allow_sign_up: true + # config_file: /etc/grafana/ldap.toml + + ## Grafana's LDAP configuration + ## Templated by the template in _helpers.tpl + ## NOTE: To enable the grafana.ini must be configured with auth.ldap.enabled + ## ref: http://docs.grafana.org/installation/configuration/#auth-ldap + ## ref: http://docs.grafana.org/installation/ldap/#configuration + ldap: + # `existingSecret` is a reference to an existing secret containing the ldap configuration + # for Grafana in a key `ldap-toml`. + existingSecret: "" + # `config` is the content of `ldap.toml` that will be stored in the created secret + config: "" + # config: |- + # verbose_logging = true + + # [[servers]] + # host = "my-ldap-server" + # port = 636 + # use_ssl = true + # start_tls = false + # ssl_skip_verify = false + # bind_dn = "uid=%s,ou=users,dc=myorg,dc=com" + + ## Grafana's SMTP configuration + ## NOTE: To enable, grafana.ini must be configured with smtp.enabled + ## ref: http://docs.grafana.org/installation/configuration/#smtp + smtp: + # `existingSecret` is a reference to an existing secret containing the smtp configuration + # for Grafana in keys `user` and `password`. + existingSecret: "" + + ## Grafana sidecars that collect the configmaps with specified label and stores the included files them into the respective folders + ## Requires at least Grafana 5 to work and can't be used together with parameters dashboardProviders, datasources and dashboards + sidecar: + image: + repository: kiwigrid/k8s-sidecar + tag: 1.27.2 + pullPolicy: IfNotPresent + resources: {} + dashboards: + enabled: true + # label that the configmaps with dashboards are marked with + label: grafana_dashboard + labelValue: "1" + # set sidecar ERROR_THROTTLE_SLEEP env var from default 5s to 0s -> fixes https://github.com/kubecost/cost-analyzer-helm-chart/issues/877 + annotations: {} + error_throttle_sleep: 0 + folder: /tmp/dashboards + datasources: + # dataSourceFilename: foo.yml # If you need to change the name of the datasource file + enabled: false + error_throttle_sleep: 0 + # label that the configmaps with datasources are marked with + label: grafana_datasource + + ## Grafana's primary configuration + ## NOTE: values in map will be converted to ini format + ## ref: http://docs.grafana.org/installation/configuration/ + ## + ## For grafana to be accessible, add the path to root_url. For example, if you run kubecost at www.foo.com:9090/kubecost + ## set root_url to "%(protocol)s://%(domain)s:%(http_port)s/kubecost/grafana". No change is necessary here if kubecost runs at a root URL + grafana.ini: + server: + serve_from_sub_path: false # Set to false on Grafana v10+ + root_url: "%(protocol)s://%(domain)s:%(http_port)s/grafana" + paths: + data: /var/lib/grafana/data + logs: /var/log/grafana + plugins: /var/lib/grafana/plugins + provisioning: /etc/grafana/provisioning + analytics: + check_for_updates: true + log: + mode: console + grafana_net: + url: https://grafana.net + auth.anonymous: + enabled: true + org_role: Editor + org_name: Main Org. + +serviceAccount: + create: true # Set this to false if you're bringing your own service account. + annotations: {} + # name: kc-test + +awsstore: + useAwsStore: false + imageNameAndVersion: gcr.io/kubecost1/awsstore:latest # Name and version of the container image for AWSStore. + createServiceAccount: false + ## PriorityClassName + ## Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass + priorityClassName: "" + # Use a custom nodeSelector for AWSStore. + nodeSelector: {} + # kubernetes.io/arch: amd64 + ## Annotations for the AWSStore ServiceAccount. + annotations: {} + +## Federated ETL Architecture +## Ref: https://docs.kubecost.com/install-and-configure/install/multi-cluster/federated-etl +## +federatedETL: + + ## If true, installs the minimal set of components required for a Federated ETL cluster. + agentOnly: false + + ## If true, push ETL data to the federated storage bucket + federatedCluster: false + + ## If true, this cluster will be able to read from the federated-store but will + ## not write to it. This is useful in situations when you want to deploy a + ## primary cluster, but don't want the primary cluster's ETL data to be + ## pushed to the bucket + readOnlyPrimary: false + + ## If true, changes the dir of S3 backup to the Federated combined store. + ## Commonly used when transitioning from Thanos to Federated ETL architecture. + redirectS3Backup: false + + ## If true, will query metrics from a central PromQL DB (e.g. Amazon Managed + ## Prometheus) + useMultiClusterDB: false + +## Kubecost Admission Controller (beta feature) +## To use this feature, ensure you have run the `create-admission-controller.sh` +## script. This generates a k8s secret with TLS keys/certificats and a +## corresponding CA bundle. +## +kubecostAdmissionController: + enabled: false + secretName: webhook-server-tls + caBundle: ${CA_BUNDLE} + +# Enables or disables the Cost Event Audit pipeline, which tracks recent changes at cluster level +# and provides an estimated cost impact via the Kubecost Predict API. +# +# It is disabled by default to avoid problems in high-scale environments. +costEventsAudit: + enabled: false + +## Disable updates to kubecost from the frontend UI and via POST request +## This feature is considered beta, entrprise users should use teams: +## https://docs.kubecost.com/using-kubecost/navigating-the-kubecost-ui/teams +# readonly: false + +# # These configs can also be set from the Settings page in the Kubecost product +# # UI. Values in this block override config changes in the Settings UI on pod +# # restart +# kubecostProductConfigs: +# # An optional list of cluster definitions that can be added for frontend +# # access. The local cluster is *always* included by default, so this list is +# # for non-local clusters. +# clusters: +# - name: "Cluster A" +# address: http://cluster-a.kubecost.com:9090 +# # Optional authentication credentials - only basic auth is currently supported. +# auth: +# type: basic +# # Secret name should be a secret formatted based on: https://github.com/kubecost/docs/blob/main/ingress-examples.md +# secretName: cluster-a-auth +# # Or pass auth directly as base64 encoded user:pass +# data: YWRtaW46YWRtaW4= +# # Or user and pass directly +# user: admin +# pass: admin +# - name: "Cluster B" +# address: http://cluster-b.kubecost.com:9090 +# # Enabling customPricesEnabled and defaultModelPricing instructs Kubecost to +# # use these custom monthly resource prices when reporting node costs. Note, +# # that the below configuration is for the monthly cost of the resource. +# # Kubecost considers there to be 730 hours in a month. Also note, that these +# # configurations will have no effect on metrics emitted such as +# # `node_ram_hourly_cost` or `node_cpu_hourly_cost`. +# # Ref: https://docs.kubecost.com/install-and-configure/install/provider-installations/air-gapped +# customPricesEnabled: false +# defaultModelPricing: +# enabled: true +# CPU: "28.0" +# spotCPU: "4.86" +# RAM: "3.09" +# spotRAM: "0.65" +# GPU: "693.50" +# spotGPU: "225.0" +# storage: "0.04" +# zoneNetworkEgress: "0.01" +# regionNetworkEgress: "0.01" +# internetNetworkEgress: "0.12" +# # The cluster profile represents a predefined set of parameters to use when calculating savings. +# # Possible values are: [ development, production, high-availability ] +# clusterProfile: production +# spotLabel: lifecycle +# spotLabelValue: Ec2Spot +# gpuLabel: gpu +# gpuLabelValue: true +# alibabaServiceKeyName: "" +# alibabaServiceKeyPassword: "" +# awsServiceKeyName: ACCESSKEYID +# awsServiceKeyPassword: fakepassword # Only use if your values.yaml are stored encrypted. Otherwise provide an existing secret via serviceKeySecretName +# awsSpotDataRegion: us-east-1 +# awsSpotDataBucket: spot-data-feed-s3-bucket +# awsSpotDataPrefix: dev +# athenaProjectID: "530337586277" # The AWS AccountID where the Athena CUR is. Generally your masterpayer account +# athenaBucketName: "s3://aws-athena-query-results-530337586277-us-east-1" +# athenaRegion: us-east-1 +# athenaDatabase: athenacurcfn_athena_test1 +# athenaTable: "athena_test1" +# athenaWorkgroup: "primary" # The default workgroup in AWS is 'primary' +# masterPayerARN: "" +# projectID: "123456789" # Also known as AccountID on AWS -- the current account/project that this instance of Kubecost is deployed on. +# gcpSecretName: gcp-secret # Name of a secret representing the gcp service key +# gcpSecretKeyName: compute-viewer-kubecost-key.json # Name of the secret's key containing the gcp service key +# bigQueryBillingDataDataset: billing_data.gcp_billing_export_v1_01AC9F_74CF1D_5565A2 +# labelMappingConfigs: # names of k8s labels or annotations used to designate different allocation concepts +# enabled: true +# owner_label: "owner" +# team_label: "team" +# department_label: "dept" +# product_label: "product" +# environment_label: "env" +# namespace_external_label: "kubernetes_namespace" # external labels/tags are used to map external cloud costs to kubernetes concepts +# cluster_external_label: "kubernetes_cluster" +# controller_external_label: "kubernetes_controller" +# product_external_label: "kubernetes_label_app" +# service_external_label: "kubernetes_service" +# deployment_external_label: "kubernetes_deployment" +# owner_external_label: "kubernetes_label_owner" +# team_external_label: "kubernetes_label_team" +# environment_external_label: "kubernetes_label_env" +# department_external_label: "kubernetes_label_department" +# statefulset_external_label: "kubernetes_statefulset" +# daemonset_external_label: "kubernetes_daemonset" +# pod_external_label: "kubernetes_pod" +# grafanaURL: "" +# # Provide a mapping from Account ID to a readable Account Name in a key/value object. Provide Account IDs as they are displayed in CloudCost +# # as the 'key' and the Account Name associated with it as the 'value' +# cloudAccountMapping: +# EXAMPLE_ACCOUNT_ID: EXAMPLE_ACCOUNT_NAME +# clusterName: "" # clusterName is the default context name in settings. +# clusterAccountID: "" # Manually set Account property for assets +# currencyCode: "USD" # official support for USD, AUD, BRL, CAD, CHF, CNY, DKK, EUR, GBP, IDR, INR, JPY, NOK, PLN, SEK +# azureBillingRegion: US # Represents 2-letter region code, e.g. West Europe = NL, Canada = CA. ref: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes +# azureSubscriptionID: 0bd50fdf-c923-4e1e-850c-196dd3dcc5d3 +# azureClientID: f2ef6f7d-71fb-47c8-b766-8d63a19db017 +# azureTenantID: 72faf3ff-7a3f-4597-b0d9-7b0b201bb23a +# azureClientPassword: fake key # Only use if your values.yaml are stored encrypted. Otherwise provide an existing secret via serviceKeySecretName +# azureOfferDurableID: "MS-AZR-0003p" +# discount: "" # percentage discount applied to compute +# negotiatedDiscount: "" # custom negotiated cloud provider discount +# defaultIdle: false +# serviceKeySecretName: "" # Use an existing AWS or Azure secret with format as in aws-service-key-secret.yaml or azure-service-key-secret.yaml. Leave blank if using createServiceKeySecret +# createServiceKeySecret: true # Creates a secret representing your cloud service key based on data in values.yaml. If you are storing unencrypted values, add a secret manually +# sharedNamespaces: "" # namespaces with shared workloads, example value: "kube-system\,ingress-nginx\,kubecost\,monitoring" +# sharedOverhead: "" # value representing a fixed external cost per month to be distributed among aggregations. +# shareTenancyCosts: true # enable or disable sharing costs such as cluster management fees (defaults to "true" on Settings page) +# metricsConfigs: # configuration for metrics emitted by Kubecost +# disabledMetrics: [] # list of metrics that Kubecost will not emit. Note that disabling metrics can lead to unexpected behavior in the cost-model. +# productKey: # Apply enterprise product license +# enabled: false +# key: "" +# secretname: productkeysecret # Reference an existing k8s secret created from a file named productkey.json of format { "key": "enterprise-key-here" }. If the secretname is specified, a configmap with the key will not be created. +# mountPath: "/some/custom/path/productkey.json" # (use instead of secretname) Declare the path at which the product key file is mounted (eg. by a secrets provisioner). The file must be of format { "key": "enterprise-key-here" }. +# # The following block enables the use of a custom SMTP server which overrides Kubecost's built-in, external SMTP server for alerts and reports +# smtp: +# config: | +# { +# "sender_email": "", +# "host": "", +# "port": 587, +# "authentication": true, +# "username": "", +# "password": "", +# "secure": true +# } +# secretname: smtpconfigsecret # Reference an existing k8s secret created from a file named smtp.json of format specified by config above. If the secretname is specified, a configmap with the key will not be created. +# mountPath: "/some/custom/path/smtp.json" # (use instead of secretname) Declare the path at which the SMTP config file is mounted (eg. by a secrets provisioner). The file must be of format specified by config above. +# carbonEstimates: false # Enables Kubecost beta carbon estimation endpoints /assets/carbon and /allocations/carbon + + ## Specify an existing Kubernetes Secret holding the cloud integration information. This Secret must contain + ## a key with name `cloud-integration.json` and the contents must be in a specific format. It is expected + ## to exist in the release Namespace. This is mutually exclusive with cloudIntegrationJSON where only one must be defined. + # cloudIntegrationSecret: "cloud-integration" + + ## Specify the cloud integration information in JSON form if pointing to an existing Secret is not desired or you'd rather + ## define the cloud integration information directly in the values file. This will result in a new Secret being created + ## named `cloud-integration` in the release Namespace. It is mutually exclusive with the cloudIntegrationSecret where only one must be defined. + # cloudIntegrationJSON: |- + # { + # "aws": [ + # { + # "athenaBucketName": "s3://AWS_cloud_integration_athenaBucketName", + # "athenaRegion": "AWS_cloud_integration_athenaRegion", + # "athenaDatabase": "AWS_cloud_integration_athenaDatabase", + # "athenaTable": "AWS_cloud_integration_athenaBucketName", + # "projectID": "AWS_cloud_integration_athena_projectID", + # "serviceKeyName": "AWS_cloud_integration_athena_serviceKeyName", + # "serviceKeySecret": "AWS_cloud_integration_athena_serviceKeySecret" + # } + # ], + # "azure": [ + # { + # "azureSubscriptionID": "my-subscription-id", + # "azureStorageAccount": "my-storage-account", + # "azureStorageAccessKey": "my-storage-access-key", + # "azureStorageContainer": "my-storage-container" + # } + # ], + # "gcp": [ + # { + # "projectID": "my-project-id", + # "billingDataDataset": "detailedbilling.my-billing-dataset", + # "key": { + # "type": "service_account", + # "project_id": "my-project-id", + # "private_key_id": "my-private-key-id", + # "private_key": "my-pem-encoded-private-key", + # "client_email": "my-service-account-name@my-project-id.iam.gserviceaccount.com", + # "client_id": "my-client-id", + # "auth_uri": "auth-uri", + # "token_uri": "token-uri", + # "auth_provider_x509_cert_url": "my-x509-provider-cert", + # "client_x509_cert_url": "my-x509-cert-url" + # } + # } + # ] + # } + + # ingestPodUID: false # Enables using UIDs to uniquely ID pods. This requires either Kubecost's replicated KSM metrics, or KSM v2.1.0+. This may impact performance, and changes the default cost-model allocation behavior. + # regionOverrides: "region1,region2,region3" # list of regions which will override default costmodel provider regions + +# Explicit names of various ConfigMaps to use. If not set, a default will apply. +# pricingConfigmapName: "" +# productConfigmapName: "" +# smtpConfigmapName: "" + +# -- Array of extra K8s manifests to deploy +## Note: Supports use of custom Helm templates +extraObjects: [] +# Cloud Billing Integration: +# - apiVersion: v1 +# kind: Secret +# metadata: +# name: cloud-integration +# namespace: kubecost +# type: Opaque +# data: +# cloud-integration.json: BASE64_SECRET +# Istio: +# - apiVersion: networking.istio.io/v1alpha3 +# kind: VirtualService +# metadata: +# name: my-virtualservice +# spec: +# hosts: +# - kubecost.myorg.com +# gateways: +# - my-gateway +# http: +# - route: +# - destination: +# host: kubecost.kubecost.svc.cluster.local +# port: +# number: 80 diff --git a/charts/nats/nats/1.2.3/.helmignore b/charts/nats/nats/1.2.3/.helmignore new file mode 100644 index 0000000000..240dfde2a0 --- /dev/null +++ b/charts/nats/nats/1.2.3/.helmignore @@ -0,0 +1,26 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ + +# template tests +/test diff --git a/charts/nats/nats/1.2.3/Chart.yaml b/charts/nats/nats/1.2.3/Chart.yaml new file mode 100644 index 0000000000..353fb0b817 --- /dev/null +++ b/charts/nats/nats/1.2.3/Chart.yaml @@ -0,0 +1,22 @@ +annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: NATS Server + catalog.cattle.io/kube-version: '>=1.16-0' + catalog.cattle.io/release-name: nats +apiVersion: v2 +appVersion: 2.10.19 +description: A Helm chart for the NATS.io High Speed Cloud Native Distributed Communications + Technology. +home: http://github.com/nats-io/k8s +icon: file://assets/icons/nats.png +keywords: +- nats +- messaging +- cncf +kubeVersion: '>=1.16-0' +maintainers: +- email: info@nats.io + name: The NATS Authors + url: https://github.com/nats-io +name: nats +version: 1.2.3 diff --git a/charts/nats/nats/1.2.3/README.md b/charts/nats/nats/1.2.3/README.md new file mode 100644 index 0000000000..0916999df1 --- /dev/null +++ b/charts/nats/nats/1.2.3/README.md @@ -0,0 +1,329 @@ +# NATS Server + +--- + +[NATS](https://nats.io) is a simple, secure and performant communications system for digital systems, services and devices. +NATS is part of the Cloud Native Computing Foundation ([CNCF](https://cncf.io)). +NATS has over [30 client language implementations](https://nats.io/download/), and its server can run on-premise, in the cloud, at the edge, and even on a Raspberry Pi. +NATS can secure and simplify design and operation of modern distributed systems. + +```shell +helm repo add nats https://nats-io.github.io/k8s/helm/charts/ +helm upgrade --install nats nats/nats +``` + +## Upgrade Nodes + +- **Upgrading from 0.x**: The `values.yaml` schema changed significantly from 0.x to 1.x. Read [UPGRADING.md](UPGRADING.md) for instructions on upgrading a 0.x release to 1.x. + +## Values + +There are a handful of explicitly defined options which are documented with comments in the [values.yaml](values.yaml) file. + +Everything in the NATS Config or Kubernetes Resources can be overridden by `merge` and `patch`, which is supported for the following values: + +| key | type | enabled by default | +|----------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------| +| `config` | [NATS Config](https://docs.nats.io/running-a-nats-service/configuration) | yes | +| `config.cluster` | [NATS Cluster](https://docs.nats.io/running-a-nats-service/configuration/clustering/cluster_config) | no | +| `config.cluster.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.jetstream` | [NATS JetStream](https://docs.nats.io/running-a-nats-service/configuration#jetstream) | no | +| `config.jetstream.fileStore.pvc` | [k8s PVC](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#persistentvolumeclaim-v1-core) | yes, when `config.jetstream` is enabled | +| `config.nats.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.leafnodes` | [NATS LeafNodes](https://docs.nats.io/running-a-nats-service/configuration/leafnodes/leafnode_conf) | no | +| `config.leafnodes.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.websocket` | [NATS WebSocket](https://docs.nats.io/running-a-nats-service/configuration/websocket/websocket_conf) | no | +| `config.websocket.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.websocket.ingress` | [k8s Ingress](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#ingress-v1-networking-k8s-io) | no | +| `config.mqtt` | [NATS MQTT](https://docs.nats.io/running-a-nats-service/configuration/mqtt/mqtt_config) | no | +| `config.mqtt.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.gateway` | [NATS Gateway](https://docs.nats.io/running-a-nats-service/configuration/gateways/gateway#gateway-configuration-block) | no | +| `config.gateway.tls` | [NATS TLS](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls) | no | +| `config.resolver` | [NATS Resolver](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/jwt/resolver) | no | +| `config.resolver.pvc` | [k8s PVC](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#persistentvolumeclaim-v1-core) | yes, when `config.resolver` is enabled | +| `container` | nats [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | yes | +| `reloader` | config reloader [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | yes | +| `promExporter` | prometheus exporter [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | no | +| `promExporter.podMonitor` | [prometheus PodMonitor](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor) | no | +| `service` | [k8s Service](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#service-v1-core) | yes | +| `statefulSet` | [k8s StatefulSet](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#statefulset-v1-apps) | yes | +| `podTemplate` | [k8s PodTemplate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#pod-v1-core) | yes | +| `headlessService` | [k8s Service](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#service-v1-core) | yes | +| `configMap` | [k8s ConfigMap](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#configmap-v1-core) | yes | +| `natsBox.contexts.default` | [NATS Context](https://docs.nats.io/using-nats/nats-tools/nats_cli#nats-contexts) | yes | +| `natsBox.contexts.[name]` | [NATS Context](https://docs.nats.io/using-nats/nats-tools/nats_cli#nats-contexts) | no | +| `natsBox.container` | nats-box [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | yes | +| `natsBox.deployment` | [k8s Deployment](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#deployment-v1-apps) | yes | +| `natsBox.podTemplate` | [k8s PodTemplate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#pod-v1-core) | yes | +| `natsBox.contextsSecret` | [k8s Secret](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#secret-v1-core) | yes | +| `natsBox.contentsSecret` | [k8s Secret](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#secret-v1-core) | yes | + +### Merge + +Merging is performed using the Helm `merge` function. Example - add NATS accounts and container resources: + +```yaml +config: + merge: + accounts: + A: + users: + - {user: a, password: a} + B: + users: + - {user: b, password: b} +natsBox: + contexts: + a: + merge: {user: a, password: a} + b: + merge: {user: b, password: b} + defaultContextName: a +``` + +## Patch + +Patching is performed using [JSON Patch](https://jsonpatch.com/). Example - add additional route to end of route list: + +```yaml +config: + cluster: + enabled: true + patch: + - op: add + path: /routes/- + value: nats://demo.nats.io:6222 +``` + +## Common Configurations + +### JetStream Cluster on 3 separate hosts + +```yaml +config: + cluster: + enabled: true + replicas: 3 + jetstream: + enabled: true + fileStore: + pvc: + size: 10Gi + +podTemplate: + topologySpreadConstraints: + kubernetes.io/hostname: + maxSkew: 1 + whenUnsatisfiable: DoNotSchedule +``` + +### NATS Container Resources + +```yaml +container: + env: + # different from k8s units, suffix must be B, KiB, MiB, GiB, or TiB + # should be ~90% of memory limit + GOMEMLIMIT: 7GiB + merge: + # recommended limit is at least 2 CPU cores and 8Gi Memory for production JetStream clusters + resources: + requests: + cpu: "2" + memory: 8Gi + limits: + cpu: "2" + memory: 8Gi +``` + +### Specify Image Version + +```yaml +container: + image: + tag: x.y.z-alpine +``` + +### Operator Mode with NATS Resolver + +Run `nsc generate config --nats-resolver` and replace the `OPERATOR_JWT`, `SYS_ACCOUNT_ID`, and `SYS_ACCOUNT_JWT` with your values. +Make sure that you do not include the trailing `,` in the `SYS_ACCOUNT_JWT`. + +``` +config: + resolver: + enabled: true + merge: + type: full + interval: 2m + timeout: 1.9s + merge: + operator: OPERATOR_JWT + system_account: SYS_ACCOUNT_ID + resolver_preload: + SYS_ACCOUNT_ID: SYS_ACCOUNT_JWT +``` + + +## Accessing NATS + +The chart contains 2 services by default, `service` and `headlessService`. + +### `service` + +The `service` is intended to be accessed by NATS Clients. It is a `ClusterIP` service by default, however it can easily be changed to a different service type. + +The `nats`, `websocket`, `leafnodes`, and `mqtt` ports will be exposed through this service by default if they are enabled. + +Example: change this service type to a `LoadBalancer`: + +```yaml +service: + merge: + spec: + type: LoadBalancer +``` + +### `headlessService` + +The `headlessService` is used for NATS Servers in the Stateful Set to discover one another. It is primarily intended to be used for Cluster Route connections. + +### TLS Considerations + +The TLS Certificate used for Client Connections should have a SAN covering DNS Name that clients access the `service` at. + +The TLS Certificate used for Cluster Route Connections should have a SAN covering the DNS Name that routes access each other on the `headlessService` at. This is `*.` by default. + +## Advanced Features + +### Templating Values + +Anything in `values.yaml` can be templated: + +- maps matching the following syntax will be templated and parsed as YAML: + ```yaml + $tplYaml: | + yaml template + ``` +- maps matching the follow syntax will be templated, parsed as YAML, and spread into the parent map/slice + ```yaml + $tplYamlSpread: | + yaml template + ``` + +Example - change service name: + +```yaml +service: + name: + $tplYaml: >- + {{ include "nats.fullname" . }}-svc +``` + +### NATS Config Units and Variables + +NATS configuration extends JSON, and can represent Units and Variables. They must be wrapped in `<< >>` in order to template correctly. Example: + +```yaml +config: + merge: + authorization: + # variable + token: << $TOKEN >> + # units + max_payload: << 2MB >> +``` + +templates to the `nats.conf`: + +``` +{ + "authorization": { + "token": $TOKEN + }, + "max_payload": 2MB, + "port": 4222, + ... +} +``` + +### NATS Config Includes + +Any NATS Config key ending in `$include` will be replaced with an include directive. Included files should be in paths relative to `/etc/nats-config`. Multiple `$include` keys are supported by using a prefix, and will be sorted alphabetically. Example: + +```yaml +config: + merge: + 00$include: auth.conf + 01$include: params.conf +configMap: + merge: + data: + auth.conf: | + accounts: { + A: { + users: [ + {user: a, password: a} + ] + }, + B: { + users: [ + {user: b, password: b} + ] + }, + } + params.conf: | + max_payload: 2MB +``` + +templates to the `nats.conf`: + +``` +include auth.conf; +"port": 4222, +... +include params.conf; +``` + +### Extra Resources + +Enables adding additional arbitrary resources. Example - expose WebSocket via VirtualService in Istio: + +```yaml +config: + websocket: + enabled: true +extraResources: +- apiVersion: networking.istio.io/v1beta1 + kind: VirtualService + metadata: + namespace: + $tplYamlSpread: > + {{ include "nats.metadataNamespace" $ }} + name: + $tplYaml: > + {{ include "nats.fullname" $ | quote }} + labels: + $tplYaml: | + {{ include "nats.labels" $ }} + spec: + hosts: + - demo.nats.io + gateways: + - my-gateway + http: + - name: default + match: + - name: root + uri: + exact: / + route: + - destination: + host: + $tplYaml: > + {{ .Values.service.name | quote }} + port: + number: + $tplYaml: > + {{ .Values.config.websocket.port }} +``` diff --git a/charts/nats/nats/1.2.3/UPGRADING.md b/charts/nats/nats/1.2.3/UPGRADING.md new file mode 100644 index 0000000000..9cc1779917 --- /dev/null +++ b/charts/nats/nats/1.2.3/UPGRADING.md @@ -0,0 +1,155 @@ +# Upgrading from 0.x to 1.x + +Instructions for upgrading an existing `nats` 0.x release to 1.x. + +## Rename Immutable Fields + +There are a number of immutable fields in the NATS Stateful Set and NATS Box deployment. All 1.x `values.yaml` files targeting an existing 0.x release will require some or all of these settings: + +```yaml +config: + # required if using JetStream file storage + jetstream: + # uncomment the next line if using JetStream file storage + # enabled: true + fileStore: + pvc: + name: + $tplYaml: >- + {{ include "nats.fullname" . }}-js-pvc + # set other PVC options here to make it match 0.x, refer to values.yaml for schema + + # required if using a full or cache resolver + resolver: + # uncomment the next line if using a full or cache resolver + # enabled: true + pvc: + name: nats-jwt-pvc + # set other PVC options here to make it match 0.x, refer to values.yaml for schema + +# required +statefulSet: + patch: + - op: remove + path: /spec/selector/matchLabels/app.kubernetes.io~1component + - $tplYamlSpread: |- + {{- if and + .Values.config.jetstream.enabled + .Values.config.jetstream.fileStore.enabled + .Values.config.jetstream.fileStore.pvc.enabled + .Values.config.resolver.enabled + .Values.config.resolver.pvc.enabled + }} + - op: move + from: /spec/volumeClaimTemplates/0 + path: /spec/volumeClaimTemplates/1 + {{- else}} + [] + {{- end }} + +# required +headlessService: + name: + $tplYaml: >- + {{ include "nats.fullname" . }} + +# required unless 0.x values explicitly set nats.serviceAccount.create=false +serviceAccount: + enabled: true + +# required to use new ClusterIP service for Clients accessing NATS +# if using TLS, this may require adding another SAN +service: + # uncomment the next line to disable the new ClusterIP service + # enabled: false + name: + $tplYaml: >- + {{ include "nats.fullname" . }}-svc + +# required if using NatsBox +natsBox: + deployment: + patch: + - op: replace + path: /spec/selector/matchLabels + value: + app: nats-box + - op: add + path: /spec/template/metadata/labels/app + value: nats-box +``` + +## Update NATS Config to new values.yaml schema + +Most values that control the NATS Config have changed and moved under the `config` key. Refer to the 1.x Chart's [values.yaml](values.yaml) for the complete schema. + +After migrating to the new values schema, ensure that changes you expect in the NATS Config files match by templating the old and new config files. + +Template your old 0.x Config Map, this example uses a file called `values-old.yaml`: + +```sh +helm template \ + --version "0.x" \ + -f values-old.yaml \ + -s templates/configmap.yaml \ + nats \ + nats/nats +``` + +Template your new 1.x Config Map, this example uses a file called `values.yaml`: + +```sh +helm template \ + --version "^1-beta" \ + -f values.yaml \ + -s templates/config-map.yaml \ + nats \ + nats/nats +``` + +## Update Kubernetes Resources to new values.yaml schema + +Most values that control Kubernetes Resources have been changed. Refer to the 1.x Chart's [values.yaml](values.yaml) for the complete schema. + +After migrating to the new values schema, ensure that changes you expect in resources match by templating the old and new resources. + +| Resource | 0.x Template File | 1.x Template File | +|-------------------------|---------------------------------|-------------------------------------------| +| Config Map | `templates/configmap.yaml` | `templates/config-map.yaml` | +| Stateful Set | `templates/statefulset.yaml` | `templates/stateful-set.yaml` | +| Headless Service | `templates/service.yaml` | `templates/headless-service.yaml` | +| ClusterIP Service | N/A | `templates/service.yaml` | +| Network Policy | `templates/networkpolicy.yaml` | N/A | +| Pod Disruption Budget | `templates/pdb.yaml` | `templates/pod-disruption-budget.yaml` | +| Service Account | `templates/rbac.yaml` | `templates/service-account.yaml` | +| Resource | `templates/` | `templates/` | +| Resource | `templates/` | `templates/` | +| Prometheus Monitor | `templates/serviceMonitor.yaml` | `templates/pod-monitor.yaml` | +| NatsBox Deployment | `templates/nats-box.yaml` | `templates/nats-box/deployment.yaml` | +| NatsBox Service Account | N/A | `templates/nats-box/service-account.yaml` | +| NatsBox Contents Secret | N/A | `templates/nats-box/contents-secret.yaml` | +| NatsBox Contexts Secret | N/A | `templates/nats-box/contexts-secret.yaml` | + +For example, to check that the Stateful Set matches: + +Template your old 0.x Stateful Set, this example uses a file called `values-old.yaml`: + +```sh +helm template \ + --version "0.x" \ + -f values-old.yaml \ + -s templates/statefulset.yaml \ + nats \ + nats/nats +``` + +Template your new 1.x Stateful Set, this example uses a file called `values.yaml`: + +```sh +helm template \ + --version "^1-beta" \ + -f values.yaml \ + -s templates/stateful-set.yaml \ + nats \ + nats/nats +``` diff --git a/charts/nats/nats/1.2.3/app-readme.md b/charts/nats/nats/1.2.3/app-readme.md new file mode 100644 index 0000000000..b4511f4d55 --- /dev/null +++ b/charts/nats/nats/1.2.3/app-readme.md @@ -0,0 +1,3 @@ +# NATS Server + + [NATS](https://nats.io) is a simple, secure and performant communications system for digital systems, services and devices. NATS is part of the Cloud Native Computing Foundation ([CNCF](https://cncf.io)). NATS has over [30 client language implementations](https://nats.io/download/), and its server can run on-premise, in the cloud, at the edge, and even on a Raspberry Pi. NATS can secure and simplify design and operation of modern distributed systems. diff --git a/charts/nats/nats/1.2.3/files/config-map.yaml b/charts/nats/nats/1.2.3/files/config-map.yaml new file mode 100644 index 0000000000..89ee3c281d --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config-map.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.configMap.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +data: + nats.conf: | + {{- include "nats.formatConfig" .config | nindent 4 }} diff --git a/charts/nats/nats/1.2.3/files/config/cluster.yaml b/charts/nats/nats/1.2.3/files/config/cluster.yaml new file mode 100644 index 0000000000..719cb8ade5 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/cluster.yaml @@ -0,0 +1,32 @@ +{{- with .Values.config.cluster }} +name: {{ $.Values.statefulSet.name }} +port: {{ .port }} +no_advertise: true +routes: +{{- $proto := ternary "tls" "nats" .tls.enabled }} +{{- $auth := "" }} +{{- if and .routeURLs.user .routeURLs.password }} + {{- $auth = printf "%s:%s@" (urlquery .routeURLs.user) (urlquery .routeURLs.password) -}} +{{- end }} +{{- $domain := $.Values.headlessService.name }} +{{- if .routeURLs.useFQDN }} + {{- $domain = printf "%s.%s.svc.%s" $domain (include "nats.namespace" $) .routeURLs.k8sClusterDomain }} +{{- end }} +{{- $port := (int .port) }} +{{- range $i, $_ := until (int .replicas) }} +- {{ printf "%s://%s%s-%d.%s:%d" $proto $auth $.Values.statefulSet.name $i $domain $port }} +{{- end }} + +{{- if and .routeURLs.user .routeURLs.password }} +authorization: + user: {{ .routeURLs.user | quote }} + password: {{ .routeURLs.password | quote }} +{{- end }} + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/config.yaml b/charts/nats/nats/1.2.3/files/config/config.yaml new file mode 100644 index 0000000000..92fd96f1a2 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/config.yaml @@ -0,0 +1,114 @@ +{{- with .Values.config }} + +server_name: << $SERVER_NAME >> +lame_duck_grace_period: 10s +lame_duck_duration: 30s +pid_file: /var/run/nats/nats.pid + +######################################## +# NATS +######################################## +{{- with .nats }} +port: {{ .port }} + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} + +######################################## +# leafnodes +######################################## +{{- with .leafnodes }} +{{- if .enabled }} +leafnodes: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/leafnodes.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# websocket +######################################## +{{- with .websocket }} +{{- if .enabled }} +websocket: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/websocket.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# MQTT +######################################## +{{- with .mqtt }} +{{- if .enabled }} +mqtt: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/mqtt.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# cluster +######################################## +{{- with .cluster }} +{{- if .enabled }} +cluster: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/cluster.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# gateway +######################################## +{{- with .gateway }} +{{- if .enabled }} +gateway: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/gateway.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# monitor +######################################## +{{- with .monitor }} +{{- if .enabled }} +{{- if .tls.enabled }} +https_port: {{ .port }} +{{- else }} +http_port: {{ .port }} +{{- end }} +{{- end }} +{{- end }} + +######################################## +# profiling +######################################## +{{- with .profiling }} +{{- if .enabled }} +prof_port: {{ .port }} +{{- end }} +{{- end }} + +######################################## +# jetstream +######################################## +{{- with $.Values.config.jetstream -}} +{{- if .enabled }} +jetstream: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/jetstream.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +######################################## +# resolver +######################################## +{{- with $.Values.config.resolver -}} +{{- if .enabled }} +resolver: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/resolver.yaml" "ctx" $) .) | nindent 2 }} +{{- end }} +{{- end }} + +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/gateway.yaml b/charts/nats/nats/1.2.3/files/config/gateway.yaml new file mode 100644 index 0000000000..32d4ed9f74 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/gateway.yaml @@ -0,0 +1,11 @@ +{{- with .Values.config.gateway }} +name: {{ $.Values.statefulSet.name }} +port: {{ .port }} + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/jetstream.yaml b/charts/nats/nats/1.2.3/files/config/jetstream.yaml new file mode 100644 index 0000000000..17262f643e --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/jetstream.yaml @@ -0,0 +1,23 @@ +{{- with .Values.config.jetstream }} +{{- with .memoryStore }} +{{- if .enabled }} +{{- with .maxSize }} +max_memory_store: << {{ . }} >> +{{- end }} +{{- else }} +max_memory_store: 0 +{{- end }} +{{- end }} +{{- with .fileStore }} +{{- if .enabled }} +store_dir: {{ .dir }} +{{- if .maxSize }} +max_file_store: << {{ .maxSize }} >> +{{- else if .pvc.enabled }} +max_file_store: << {{ .pvc.size }} >> +{{- end }} +{{- else }} +max_file_store: 0 +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/leafnodes.yaml b/charts/nats/nats/1.2.3/files/config/leafnodes.yaml new file mode 100644 index 0000000000..3a1d9a14ae --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/leafnodes.yaml @@ -0,0 +1,11 @@ +{{- with .Values.config.leafnodes }} +port: {{ .port }} +no_advertise: true + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/mqtt.yaml b/charts/nats/nats/1.2.3/files/config/mqtt.yaml new file mode 100644 index 0000000000..e25d8a3e0a --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/mqtt.yaml @@ -0,0 +1,10 @@ +{{- with .Values.config.mqtt }} +port: {{ .port }} + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/protocol.yaml b/charts/nats/nats/1.2.3/files/config/protocol.yaml new file mode 100644 index 0000000000..288c80d754 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/protocol.yaml @@ -0,0 +1,10 @@ +{{- with .protocol }} +port: {{ .port }} + +{{- with .tls }} +{{- if .enabled }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/resolver.yaml b/charts/nats/nats/1.2.3/files/config/resolver.yaml new file mode 100644 index 0000000000..a6761c4030 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/resolver.yaml @@ -0,0 +1,3 @@ +{{- with .Values.config.resolver }} +dir: {{ .dir }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/tls.yaml b/charts/nats/nats/1.2.3/files/config/tls.yaml new file mode 100644 index 0000000000..26aee01558 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/tls.yaml @@ -0,0 +1,16 @@ +# tls +{{- with .tls }} +{{- if .secretName }} +{{- $dir := trimSuffix "/" .dir }} +cert_file: {{ printf "%s/%s" $dir (.cert | default "tls.crt") | quote }} +key_file: {{ printf "%s/%s" $dir (.key | default "tls.key") | quote }} +{{- end }} +{{- end }} + +# tlsCA +{{- with $.Values.tlsCA }} +{{- if and .enabled (or .configMapName .secretName) }} +{{- $dir := trimSuffix "/" .dir }} +ca_file: {{ printf "%s/%s" $dir (.key | default "ca.crt") | quote }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/config/websocket.yaml b/charts/nats/nats/1.2.3/files/config/websocket.yaml new file mode 100644 index 0000000000..afcd178a70 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/config/websocket.yaml @@ -0,0 +1,12 @@ +{{- with .Values.config.websocket }} +port: {{ .port }} + +{{- if .tls.enabled }} +{{- with .tls }} +tls: + {{- include "nats.loadMergePatch" (merge (dict "file" "config/tls.yaml" "ctx" (merge (dict "tls" .) $)) .) | nindent 2 }} +{{- end }} +{{- else }} +no_tls: true +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/headless-service.yaml b/charts/nats/nats/1.2.3/files/headless-service.yaml new file mode 100644 index 0000000000..da6552b375 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/headless-service.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.headlessService.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + selector: + {{- include "nats.selectorLabels" $ | nindent 4 }} + clusterIP: None + publishNotReadyAddresses: true + ports: + {{- range $protocol := list "nats" "leafnodes" "websocket" "mqtt" "cluster" "gateway" "monitor" "profiling" }} + {{- $configProtocol := get $.Values.config $protocol }} + {{- if or (eq $protocol "nats") $configProtocol.enabled }} + {{- $tlsEnabled := false }} + {{- if hasKey $configProtocol "tls" }} + {{- $tlsEnabled = $configProtocol.tls.enabled }} + {{- end }} + {{- $appProtocol := or (eq $protocol "websocket") (eq $protocol "monitor") | ternary ($tlsEnabled | ternary "https" "http") ($tlsEnabled | ternary "tls" "tcp") }} + - {{ dict "name" $protocol "port" $configProtocol.port "targetPort" $protocol "appProtocol" $appProtocol | toYaml | nindent 4 }} + {{- end }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/files/ingress.yaml b/charts/nats/nats/1.2.3/files/ingress.yaml new file mode 100644 index 0000000000..b59f0fa5fe --- /dev/null +++ b/charts/nats/nats/1.2.3/files/ingress.yaml @@ -0,0 +1,34 @@ +{{- with .Values.config.websocket.ingress }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + {{- with .className }} + ingressClassName: {{ . | quote }} + {{- end }} + rules: + {{- $path := .path }} + {{- $pathType := .pathType }} + {{- range .hosts }} + - host: {{ . | quote }} + http: + paths: + - path: {{ $path | quote }} + pathType: {{ $pathType | quote }} + backend: + service: + name: {{ $.Values.service.name }} + port: + name: websocket + {{- end }} + {{- if .tlsSecretName }} + tls: + - secretName: {{ .tlsSecretName | quote }} + hosts: + {{- toYaml .hosts | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/contents-secret.yaml b/charts/nats/nats/1.2.3/files/nats-box/contents-secret.yaml new file mode 100644 index 0000000000..6e8fdb26f2 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/contents-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.natsBox.contentsSecret.name }} + labels: + {{- include "natsBox.labels" $ | nindent 4 }} +type: Opaque +stringData: + {{- range $ctxKey, $ctxVal := .Values.natsBox.contexts }} + {{- range $secretKey, $secretVal := dict "creds" "creds" "nkey" "nk" }} + {{- $secret := get $ctxVal $secretKey }} + {{- if and $secret $secret.contents }} + "{{ $ctxKey }}.{{ $secretVal }}": {{ $secret.contents | quote }} + {{- end }} + {{- end }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/context.yaml b/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/context.yaml new file mode 100644 index 0000000000..54480eac99 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/context.yaml @@ -0,0 +1,51 @@ +{{- $contextName := .contextName }} + +# url +{{- if .Values.service.enabled }} +url: nats://{{ .Values.service.name }} +{{- else }} +url: nats://{{ .Values.headlessService.name }} +{{- end }} + +{{- with .context }} + +# creds +{{- with .creds}} +{{- if .contents }} +creds: /etc/nats-contents/{{ $contextName }}.creds +{{- else if .secretName }} +{{- $dir := trimSuffix "/" .dir }} +creds: {{ printf "%s/%s" $dir (.key | default "nats.creds") | quote }} +{{- end }} +{{- end }} + +# nkey +{{- with .nkey}} +{{- if .contents }} +nkey: /etc/nats-contents/{{ $contextName }}.nk +{{- else if .secretName }} +{{- $dir := trimSuffix "/" .dir }} +nkey: {{ printf "%s/%s" $dir (.key | default "nats.nk") | quote }} +{{- end }} +{{- end }} + +# tls +{{- with .tls }} +{{- if .secretName }} +{{- $dir := trimSuffix "/" .dir }} +cert: {{ printf "%s/%s" $dir (.cert | default "tls.crt") | quote }} +key: {{ printf "%s/%s" $dir (.key | default "tls.key") | quote }} +{{- end }} +{{- end }} + +# tlsCA +{{- if $.Values.config.nats.tls.enabled }} +{{- with $.Values.tlsCA }} +{{- if and .enabled (or .configMapName .secretName) }} +{{- $dir := trimSuffix "/" .dir }} +ca: {{ printf "%s/%s" $dir (.key | default "ca.crt") | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/contexts-secret.yaml b/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/contexts-secret.yaml new file mode 100644 index 0000000000..0ce8d1d87b --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/contexts-secret/contexts-secret.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.natsBox.contextsSecret.name }} + labels: + {{- include "natsBox.labels" $ | nindent 4 }} +type: Opaque +stringData: +{{- range $ctxKey, $ctxVal := .Values.natsBox.contexts }} + "{{ $ctxKey }}.json": | + {{- include "toPrettyRawJson" (include "nats.loadMergePatch" (dict "file" "nats-box/contexts-secret/context.yaml" "merge" (.merge | default dict) "patch" (.patch | default list) "ctx" (merge (dict "contextName" $ctxKey "context" $ctxVal) $)) | fromYaml) | nindent 4 }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/deployment/container.yaml b/charts/nats/nats/1.2.3/files/nats-box/deployment/container.yaml new file mode 100644 index 0000000000..aa1753b4b5 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/deployment/container.yaml @@ -0,0 +1,46 @@ +name: nats-box +{{ include "nats.image" (merge (pick $.Values "global") .Values.natsBox.container.image) }} + +{{- with .Values.natsBox.container.env }} +env: +{{- include "nats.env" . }} +{{- end }} + +command: +- sh +- -ec +- | + work_dir="$(pwd)" + mkdir -p "$XDG_CONFIG_HOME/nats" + cd "$XDG_CONFIG_HOME/nats" + if ! [ -s context ]; then + ln -s /etc/nats-contexts context + fi + {{- if .Values.natsBox.defaultContextName }} + if ! [ -f context.txt ]; then + echo -n {{ .Values.natsBox.defaultContextName | quote }} > context.txt + fi + {{- end }} + cd "$work_dir" + exec /entrypoint.sh "$@" +- -- +args: +- sh +- -ec +- trap true INT TERM; sleep infinity & wait +volumeMounts: +# contexts secret +- name: contexts + mountPath: /etc/nats-contexts +# contents secret +{{- if .hasContentsSecret }} +- name: contents + mountPath: /etc/nats-contents +{{- end }} +# tlsCA +{{- include "nats.tlsCAVolumeMount" $ }} +# secrets +{{- range (include "natsBox.secretNames" $ | fromJson).secretNames }} +- name: {{ .name | quote }} + mountPath: {{ .dir | quote }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/deployment/deployment.yaml b/charts/nats/nats/1.2.3/files/nats-box/deployment/deployment.yaml new file mode 100644 index 0000000000..bf39dd8d53 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/deployment/deployment.yaml @@ -0,0 +1,16 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.natsBox.deployment.name }} + labels: + {{- include "natsBox.labels" $ | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "natsBox.selectorLabels" $ | nindent 6 }} + replicas: 1 + template: + {{- with .Values.natsBox.podTemplate }} + {{ include "nats.loadMergePatch" (merge (dict "file" "nats-box/deployment/pod-template.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/deployment/pod-template.yaml b/charts/nats/nats/1.2.3/files/nats-box/deployment/pod-template.yaml new file mode 100644 index 0000000000..71056bfb61 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/deployment/pod-template.yaml @@ -0,0 +1,44 @@ +metadata: + labels: + {{- include "natsBox.labels" $ | nindent 4 }} +spec: + containers: + {{- with .Values.natsBox.container }} + - {{ include "nats.loadMergePatch" (merge (dict "file" "nats-box/deployment/container.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} + + # service discovery uses DNS; don't need service env vars + enableServiceLinks: false + + {{- with .Values.global.image.pullSecretNames }} + imagePullSecrets: + {{- range . }} + - name: {{ . | quote }} + {{- end }} + {{- end }} + + {{- with .Values.natsBox.serviceAccount }} + {{- if .enabled }} + serviceAccountName: {{ .name | quote }} + {{- end }} + {{- end }} + + volumes: + # contexts secret + - name: contexts + secret: + secretName: {{ .Values.natsBox.contextsSecret.name }} + # contents secret + {{- if .hasContentsSecret }} + - name: contents + secret: + secretName: {{ .Values.natsBox.contentsSecret.name }} + {{- end }} + # tlsCA + {{- include "nats.tlsCAVolume" $ | nindent 2 }} + # secrets + {{- range (include "natsBox.secretNames" $ | fromJson).secretNames }} + - name: {{ .name | quote }} + secret: + secretName: {{ .secretName | quote }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/files/nats-box/service-account.yaml b/charts/nats/nats/1.2.3/files/nats-box/service-account.yaml new file mode 100644 index 0000000000..c31e52f18e --- /dev/null +++ b/charts/nats/nats/1.2.3/files/nats-box/service-account.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.natsBox.serviceAccount.name }} + labels: + {{- include "natsBox.labels" $ | nindent 4 }} diff --git a/charts/nats/nats/1.2.3/files/pod-disruption-budget.yaml b/charts/nats/nats/1.2.3/files/pod-disruption-budget.yaml new file mode 100644 index 0000000000..fd1fdead52 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/pod-disruption-budget.yaml @@ -0,0 +1,12 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.podDisruptionBudget.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + {{- include "nats.selectorLabels" $ | nindent 6 }} diff --git a/charts/nats/nats/1.2.3/files/pod-monitor.yaml b/charts/nats/nats/1.2.3/files/pod-monitor.yaml new file mode 100644 index 0000000000..c6c8eae069 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/pod-monitor.yaml @@ -0,0 +1,13 @@ +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.promExporter.podMonitor.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "nats.selectorLabels" $ | nindent 6 }} + podMetricsEndpoints: + - port: prom-metrics diff --git a/charts/nats/nats/1.2.3/files/service-account.yaml b/charts/nats/nats/1.2.3/files/service-account.yaml new file mode 100644 index 0000000000..22c18cc700 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/service-account.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.serviceAccount.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} diff --git a/charts/nats/nats/1.2.3/files/service.yaml b/charts/nats/nats/1.2.3/files/service.yaml new file mode 100644 index 0000000000..db08fe5b59 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/service.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Service +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.service.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + selector: + {{- include "nats.selectorLabels" $ | nindent 4 }} + ports: + {{- range $protocol := list "nats" "leafnodes" "websocket" "mqtt" "cluster" "gateway" "monitor" "profiling" }} + {{- $configProtocol := get $.Values.config $protocol }} + {{- $servicePort := get $.Values.service.ports $protocol }} + {{- if and (or (eq $protocol "nats") $configProtocol.enabled) $servicePort.enabled }} + {{- $tlsEnabled := false }} + {{- if hasKey $configProtocol "tls" }} + {{- $tlsEnabled = $configProtocol.tls.enabled }} + {{- end }} + {{- $appProtocol := or (eq $protocol "websocket") (eq $protocol "monitor") | ternary ($tlsEnabled | ternary "https" "http") ($tlsEnabled | ternary "tls" "tcp") }} + - {{ merge (dict "name" $protocol "targetPort" $protocol "appProtocol" $appProtocol) (omit $servicePort "enabled") (dict "port" $configProtocol.port) | toYaml | nindent 4 }} + {{- end }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/jetstream-pvc.yaml b/charts/nats/nats/1.2.3/files/stateful-set/jetstream-pvc.yaml new file mode 100644 index 0000000000..a43f200595 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/jetstream-pvc.yaml @@ -0,0 +1,13 @@ +{{- with .Values.config.jetstream.fileStore.pvc }} +metadata: + name: {{ .name }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .size | quote }} + {{- with .storageClassName }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/nats-container.yaml b/charts/nats/nats/1.2.3/files/stateful-set/nats-container.yaml new file mode 100644 index 0000000000..c5402efea4 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/nats-container.yaml @@ -0,0 +1,106 @@ +name: nats +{{ include "nats.image" (merge (pick $.Values "global") .Values.container.image) }} + +ports: +{{- range $protocol := list "nats" "leafnodes" "websocket" "mqtt" "cluster" "gateway" "monitor" "profiling" }} +{{- $configProtocol := get $.Values.config $protocol }} +{{- $containerPort := get $.Values.container.ports $protocol }} +{{- if or (eq $protocol "nats") $configProtocol.enabled }} +- {{ merge (dict "name" $protocol "containerPort" $configProtocol.port) $containerPort | toYaml | nindent 2 }} +{{- end }} +{{- end }} + +args: +- --config +- /etc/nats-config/nats.conf + +env: +- name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +- name: SERVER_NAME + value: {{ printf "%s$(POD_NAME)" .Values.config.serverNamePrefix | quote }} +{{- with .Values.container.env }} +{{- include "nats.env" . }} +{{- end }} + +lifecycle: + preStop: + exec: + # send the lame duck shutdown signal to trigger a graceful shutdown + command: + - nats-server + - -sl=ldm=/var/run/nats/nats.pid + +{{- with .Values.config.monitor }} +{{- if .enabled }} +startupProbe: + httpGet: + path: /healthz + port: monitor + {{- if .tls.enabled }} + scheme: HTTPS + {{- end}} + initialDelaySeconds: 10 + timeoutSeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 90 +readinessProbe: + httpGet: + path: /healthz?js-server-only=true + port: monitor + {{- if .tls.enabled }} + scheme: HTTPS + {{- end}} + initialDelaySeconds: 10 + timeoutSeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 +livenessProbe: + httpGet: + path: /healthz?js-enabled-only=true + port: monitor + {{- if .tls.enabled }} + scheme: HTTPS + {{- end}} + initialDelaySeconds: 10 + timeoutSeconds: 5 + periodSeconds: 30 + successThreshold: 1 + failureThreshold: 3 +{{- end }} +{{- end }} + +volumeMounts: +# nats config +- name: config + mountPath: /etc/nats-config +# PID volume +- name: pid + mountPath: /var/run/nats +# JetStream PVC +{{- with .Values.config.jetstream }} +{{- if and .enabled .fileStore.enabled .fileStore.pvc.enabled }} +{{- with .fileStore }} +- name: {{ .pvc.name }} + mountPath: {{ .dir | quote }} +{{- end }} +{{- end }} +{{- end }} +# resolver PVC +{{- with .Values.config.resolver }} +{{- if and .enabled .pvc.enabled }} +- name: {{ .pvc.name }} + mountPath: {{ .dir | quote }} +{{- end }} +{{- end }} +# tlsCA +{{- include "nats.tlsCAVolumeMount" $ }} +# secrets +{{- range (include "nats.secretNames" $ | fromJson).secretNames }} +- name: {{ .name | quote }} + mountPath: {{ .dir | quote }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/pod-template.yaml b/charts/nats/nats/1.2.3/files/stateful-set/pod-template.yaml new file mode 100644 index 0000000000..bb1d8d7bed --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/pod-template.yaml @@ -0,0 +1,71 @@ +metadata: + labels: + {{- include "nats.labels" $ | nindent 4 }} + annotations: + {{- if .Values.podTemplate.configChecksumAnnotation }} + {{- $configMap := include "nats.loadMergePatch" (merge (dict "file" "config-map.yaml" "ctx" $) $.Values.configMap) }} + checksum/config: {{ sha256sum $configMap }} + {{- end }} +spec: + containers: + # nats + {{- $nats := dict }} + {{- with .Values.container }} + {{- $nats = include "nats.loadMergePatch" (merge (dict "file" "stateful-set/nats-container.yaml" "ctx" $) .) | fromYaml }} + - {{ toYaml $nats | nindent 4 }} + {{- end }} + # reloader + {{- with .Values.reloader }} + {{- if .enabled }} + - {{ include "nats.loadMergePatch" (merge (dict "file" "stateful-set/reloader-container.yaml" "ctx" (merge (dict "natsVolumeMounts" $nats.volumeMounts) $)) .) | nindent 4 }} + {{- end }} + {{- end }} + {{- with .Values.promExporter }} + {{- if .enabled }} + - {{ include "nats.loadMergePatch" (merge (dict "file" "stateful-set/prom-exporter-container.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} + {{- end }} + + # service discovery uses DNS; don't need service env vars + enableServiceLinks: false + + {{- with .Values.global.image.pullSecretNames }} + imagePullSecrets: + {{- range . }} + - name: {{ . | quote }} + {{- end }} + {{- end }} + + {{- with .Values.serviceAccount }} + {{- if .enabled }} + serviceAccountName: {{ .name | quote }} + {{- end }} + {{- end }} + + {{- if .Values.reloader.enabled }} + shareProcessNamespace: true + {{- end }} + + volumes: + # nats config + - name: config + configMap: + name: {{ .Values.configMap.name }} + # PID volume + - name: pid + emptyDir: {} + # tlsCA + {{- include "nats.tlsCAVolume" $ | nindent 2 }} + # secrets + {{- range (include "nats.secretNames" $ | fromJson).secretNames }} + - name: {{ .name | quote }} + secret: + secretName: {{ .secretName | quote }} + {{- end }} + + {{- with .Values.podTemplate.topologySpreadConstraints }} + topologySpreadConstraints: + {{- range $k, $v := . }} + - {{ merge (dict "topologyKey" $k "labelSelector" (dict "matchLabels" (include "nats.selectorLabels" $ | fromYaml))) $v | toYaml | nindent 4 }} + {{- end }} + {{- end}} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/prom-exporter-container.yaml b/charts/nats/nats/1.2.3/files/stateful-set/prom-exporter-container.yaml new file mode 100644 index 0000000000..c3e1b6fbe6 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/prom-exporter-container.yaml @@ -0,0 +1,30 @@ +name: prom-exporter +{{ include "nats.image" (merge (pick $.Values "global") .Values.promExporter.image) }} + +ports: +- name: prom-metrics + containerPort: {{ .Values.promExporter.port }} + +{{- with .Values.promExporter.env }} +env: +{{- include "nats.env" . }} +{{- end }} + +args: +- -port={{ .Values.promExporter.port }} +- -connz +- -routez +- -subz +- -varz +- -prefix=nats +- -use_internal_server_id +{{- if .Values.config.jetstream.enabled }} +- -jsz=all +{{- end }} +{{- if .Values.config.leafnodes.enabled }} +- -leafz +{{- end }} +{{- if .Values.config.gateway.enabled }} +- -gatewayz +{{- end }} +- http://localhost:{{ .Values.config.monitor.port }}/ diff --git a/charts/nats/nats/1.2.3/files/stateful-set/reloader-container.yaml b/charts/nats/nats/1.2.3/files/stateful-set/reloader-container.yaml new file mode 100644 index 0000000000..96722045fb --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/reloader-container.yaml @@ -0,0 +1,27 @@ +name: reloader +{{ include "nats.image" (merge (pick $.Values "global") .Values.reloader.image) }} + +{{- with .Values.reloader.env }} +env: +{{- include "nats.env" . }} +{{- end }} + +args: +- -pid +- /var/run/nats/nats.pid +- -config +- /etc/nats-config/nats.conf +{{ include "nats.reloaderConfig" (dict "config" .config "dir" "/etc/nats-config") }} + +volumeMounts: +- name: pid + mountPath: /var/run/nats +{{- range $mnt := .natsVolumeMounts }} +{{- $found := false }} +{{- range $.Values.reloader.natsVolumeMountPrefixes }} +{{- if and (not $found) (hasPrefix . $mnt.mountPath) }} +{{- $found = true }} +- {{ toYaml $mnt | nindent 2}} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/resolver-pvc.yaml b/charts/nats/nats/1.2.3/files/stateful-set/resolver-pvc.yaml new file mode 100644 index 0000000000..3634cd826d --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/resolver-pvc.yaml @@ -0,0 +1,13 @@ +{{- with .Values.config.resolver.pvc }} +metadata: + name: {{ .name }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .size | quote }} + {{- with .storageClassName }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/files/stateful-set/stateful-set.yaml b/charts/nats/nats/1.2.3/files/stateful-set/stateful-set.yaml new file mode 100644 index 0000000000..cd8082cbb5 --- /dev/null +++ b/charts/nats/nats/1.2.3/files/stateful-set/stateful-set.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + {{- include "nats.metadataNamespace" $ | nindent 2 }} + name: {{ .Values.statefulSet.name }} + labels: + {{- include "nats.labels" $ | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "nats.selectorLabels" $ | nindent 6 }} + {{- if .Values.config.cluster.enabled }} + replicas: {{ .Values.config.cluster.replicas }} + {{- else }} + replicas: 1 + {{- end }} + serviceName: {{ .Values.headlessService.name }} + podManagementPolicy: Parallel + template: + {{- with .Values.podTemplate }} + {{ include "nats.loadMergePatch" (merge (dict "file" "stateful-set/pod-template.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} + volumeClaimTemplates: + {{- with .Values.config.jetstream }} + {{- if and .enabled .fileStore.enabled .fileStore.pvc.enabled }} + {{- with .fileStore.pvc }} + - {{ include "nats.loadMergePatch" (merge (dict "file" "stateful-set/jetstream-pvc.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.config.resolver }} + {{- if and .enabled .pvc.enabled }} + {{- with .pvc }} + - {{ include "nats.loadMergePatch" (merge (dict "file" "stateful-set/resolver-pvc.yaml" "ctx" $) .) | nindent 4 }} + {{- end }} + {{- end }} + {{- end }} diff --git a/charts/nats/nats/1.2.3/questions.yaml b/charts/nats/nats/1.2.3/questions.yaml new file mode 100644 index 0000000000..a476e440d9 --- /dev/null +++ b/charts/nats/nats/1.2.3/questions.yaml @@ -0,0 +1,12 @@ +questions: +- variable: cluster.enabled + default: false + type: boolean + label: Enable Cluster + group: "Cluster Settings" + show_subquestion_if: "true" + subquestions: + - variable: cluster.replicas + default: 3 + type: int + label: Replicas diff --git a/charts/nats/nats/1.2.3/templates/_helpers.tpl b/charts/nats/nats/1.2.3/templates/_helpers.tpl new file mode 100644 index 0000000000..ba831397d9 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/_helpers.tpl @@ -0,0 +1,281 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "nats.name" -}} +{{- default .Chart.Name .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 "nats.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nats.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Print the namespace +*/}} +{{- define "nats.namespace" -}} +{{- default .Release.Namespace .Values.namespaceOverride }} +{{- end }} + +{{/* +Print the namespace for the metadata section +*/}} +{{- define "nats.metadataNamespace" -}} +{{- with .Values.namespaceOverride }} +namespace: {{ . | quote }} +{{- end }} +{{- end }} + +{{/* +Set default values. +*/}} +{{- define "nats.defaultValues" }} +{{- if not .defaultValuesSet }} + {{- $name := include "nats.fullname" . }} + {{- with .Values }} + {{- $_ := set .config.jetstream.fileStore.pvc "name" (.config.jetstream.fileStore.pvc.name | default (printf "%s-js" $name)) }} + {{- $_ := set .config.resolver.pvc "name" (.config.resolver.pvc.name | default (printf "%s-resolver" $name)) }} + {{- $_ := set .config.websocket.ingress "name" (.config.websocket.ingress.name | default (printf "%s-ws" $name)) }} + {{- $_ := set .configMap "name" (.configMap.name | default (printf "%s-config" $name)) }} + {{- $_ := set .headlessService "name" (.headlessService.name | default (printf "%s-headless" $name)) }} + {{- $_ := set .natsBox.contentsSecret "name" (.natsBox.contentsSecret.name | default (printf "%s-box-contents" $name)) }} + {{- $_ := set .natsBox.contextsSecret "name" (.natsBox.contextsSecret.name | default (printf "%s-box-contexts" $name)) }} + {{- $_ := set .natsBox.deployment "name" (.natsBox.deployment.name | default (printf "%s-box" $name)) }} + {{- $_ := set .natsBox.serviceAccount "name" (.natsBox.serviceAccount.name | default (printf "%s-box" $name)) }} + {{- $_ := set .podDisruptionBudget "name" (.podDisruptionBudget.name | default $name) }} + {{- $_ := set .service "name" (.service.name | default $name) }} + {{- $_ := set .serviceAccount "name" (.serviceAccount.name | default $name) }} + {{- $_ := set .statefulSet "name" (.statefulSet.name | default $name) }} + {{- $_ := set .promExporter.podMonitor "name" (.promExporter.podMonitor.name | default $name) }} + {{- end }} + + {{- $values := get (include "tplYaml" (dict "doc" .Values "ctx" $) | fromJson) "doc" }} + {{- $_ := set . "Values" $values }} + + {{- $hasContentsSecret := false }} + {{- range $ctxKey, $ctxVal := .Values.natsBox.contexts }} + {{- range $secretKey, $secretVal := dict "creds" "nats-creds" "nkey" "nats-nkeys" "tls" "nats-certs" }} + {{- $secret := get $ctxVal $secretKey }} + {{- if $secret }} + {{- $_ := set $secret "dir" ($secret.dir | default (printf "/etc/%s/%s" $secretVal $ctxKey)) }} + {{- if and (ne $secretKey "tls") $secret.contents }} + {{- $hasContentsSecret = true }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- $_ := set $ "hasContentsSecret" $hasContentsSecret }} + + {{- with .Values.config }} + {{- $config := include "nats.loadMergePatch" (merge (dict "file" "config/config.yaml" "ctx" $) .) | fromYaml }} + {{- $_ := set $ "config" $config }} + {{- end }} + + {{- $_ := set . "defaultValuesSet" true }} +{{- end }} +{{- end }} + +{{/* +NATS labels +*/}} +{{- define "nats.labels" -}} +{{- with .Values.global.labels -}} +{{ toYaml . }} +{{ end -}} +helm.sh/chart: {{ include "nats.chart" . }} +{{ include "nats.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +NATS selector labels +*/}} +{{- define "nats.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nats.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: nats +{{- end }} + +{{/* +NATS Box labels +*/}} +{{- define "natsBox.labels" -}} +{{- with .Values.global.labels -}} +{{ toYaml . }} +{{ end -}} +helm.sh/chart: {{ include "nats.chart" . }} +{{ include "natsBox.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +NATS Box selector labels +*/}} +{{- define "natsBox.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nats.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: nats-box +{{- end }} + +{{/* +Print the image +*/}} +{{- define "nats.image" }} +{{- $image := printf "%s:%s" .repository .tag }} +{{- if or .registry .global.image.registry }} +{{- $image = printf "%s/%s" (.registry | default .global.image.registry) $image }} +{{- end -}} +image: {{ $image }} +{{- if or .pullPolicy .global.image.pullPolicy }} +imagePullPolicy: {{ .pullPolicy | default .global.image.pullPolicy }} +{{- end }} +{{- end }} + +{{- define "nats.secretNames" -}} +{{- $secrets := list }} +{{- range $protocol := list "nats" "leafnodes" "websocket" "mqtt" "cluster" "gateway" }} + {{- $configProtocol := get $.Values.config $protocol }} + {{- if and (or (eq $protocol "nats") $configProtocol.enabled) $configProtocol.tls.enabled $configProtocol.tls.secretName }} + {{- $secrets = append $secrets (merge (dict "name" (printf "%s-tls" $protocol)) $configProtocol.tls) }} + {{- end }} +{{- end }} +{{- toJson (dict "secretNames" $secrets) }} +{{- end }} + +{{- define "natsBox.secretNames" -}} +{{- $secrets := list }} +{{- range $ctxKey, $ctxVal := .Values.natsBox.contexts }} +{{- range $secretKey, $secretVal := dict "creds" "nats-creds" "nkey" "nats-nkeys" "tls" "nats-certs" }} + {{- $secret := get $ctxVal $secretKey }} + {{- if and $secret $secret.secretName }} + {{- $secrets = append $secrets (merge (dict "name" (printf "ctx-%s-%s" $ctxKey $secretKey)) $secret) }} + {{- end }} + {{- end }} +{{- end }} +{{- toJson (dict "secretNames" $secrets) }} +{{- end }} + +{{- define "nats.tlsCAVolume" -}} +{{- with .Values.tlsCA }} +{{- if and .enabled (or .configMapName .secretName) }} +- name: tls-ca +{{- if .configMapName }} + configMap: + name: {{ .configMapName | quote }} +{{- else if .secretName }} + secret: + secretName: {{ .secretName | quote }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "nats.tlsCAVolumeMount" -}} +{{- with .Values.tlsCA }} +{{- if and .enabled (or .configMapName .secretName) }} +- name: tls-ca + mountPath: {{ .dir | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +translates env var map to list +*/}} +{{- define "nats.env" -}} +{{- range $k, $v := . }} +{{- if kindIs "string" $v }} +- name: {{ $k | quote }} + value: {{ $v | quote }} +{{- else if kindIs "map" $v }} +- {{ merge (dict "name" $k) $v | toYaml | nindent 2 }} +{{- else }} +{{- fail (cat "env var" $k "must be string or map, got" (kindOf $v)) }} +{{- end }} +{{- end }} +{{- end }} + +{{- /* +nats.loadMergePatch +input: map with 4 keys: +- file: name of file to load +- ctx: context to pass to tpl +- merge: interface{} to merge +- patch: []interface{} valid JSON Patch document +output: JSON encoded map with 1 key: +- doc: interface{} patched json result +*/}} +{{- define "nats.loadMergePatch" -}} +{{- $doc := tpl (.ctx.Files.Get (printf "files/%s" .file)) .ctx | fromYaml | default dict -}} +{{- $doc = mergeOverwrite $doc (deepCopy (.merge | default dict)) -}} +{{- get (include "jsonpatch" (dict "doc" $doc "patch" (.patch | default list)) | fromJson ) "doc" | toYaml -}} +{{- end }} + + +{{- /* +nats.reloaderConfig +input: map with 2 keys: +- config: interface{} nats config +- dir: dir config file is in +output: YAML list of reloader config files +*/}} +{{- define "nats.reloaderConfig" -}} + {{- $dir := trimSuffix "/" .dir -}} + {{- with .config -}} + {{- if kindIs "map" . -}} + {{- range $k, $v := . -}} + {{- if or (eq $k "cert_file") (eq $k "key_file") (eq $k "ca_file") }} +- -config +- {{ $v }} + {{- else if hasSuffix "$include" $k }} +- -config +- {{ clean (printf "%s/%s" $dir $v) }} + {{- else }} + {{- include "nats.reloaderConfig" (dict "config" $v "dir" $dir) }} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + +{{- /* +nats.formatConfig +input: map[string]interface{} +output: string with following format rules +1. keys ending in $natsRaw are unquoted +2. keys ending in $natsInclude are converted to include directives +*/}} +{{- define "nats.formatConfig" -}} + {{- + (regexReplaceAll "\"<<\\s+(.*)\\s+>>\"" + (regexReplaceAll "\".*\\$include\": \"(.*)\",?" (include "toPrettyRawJson" .) "include ${1};") + "${1}") + -}} +{{- end -}} diff --git a/charts/nats/nats/1.2.3/templates/_jsonpatch.tpl b/charts/nats/nats/1.2.3/templates/_jsonpatch.tpl new file mode 100644 index 0000000000..cd42c3bbcc --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/_jsonpatch.tpl @@ -0,0 +1,219 @@ +{{- /* +jsonpatch +input: map with 2 keys: +- doc: interface{} valid JSON document +- patch: []interface{} valid JSON Patch document +output: JSON encoded map with 1 key: +- doc: interface{} patched json result +*/}} +{{- define "jsonpatch" -}} + {{- $params := fromJson (toJson .) -}} + {{- $patches := $params.patch -}} + {{- $docContainer := pick $params "doc" -}} + + {{- range $patch := $patches -}} + {{- if not (hasKey $patch "op") -}} + {{- fail "patch is missing op key" -}} + {{- end -}} + {{- if and (ne $patch.op "add") (ne $patch.op "remove") (ne $patch.op "replace") (ne $patch.op "copy") (ne $patch.op "move") (ne $patch.op "test") -}} + {{- fail (cat "patch has invalid op" $patch.op) -}} + {{- end -}} + {{- if not (hasKey $patch "path") -}} + {{- fail "patch is missing path key" -}} + {{- end -}} + {{- if and (or (eq $patch.op "add") (eq $patch.op "replace") (eq $patch.op "test")) (not (hasKey $patch "value")) -}} + {{- fail (cat "patch with op" $patch.op "is missing value key") -}} + {{- end -}} + {{- if and (or (eq $patch.op "copy") (eq $patch.op "move")) (not (hasKey $patch "from")) -}} + {{- fail (cat "patch with op" $patch.op "is missing from key") -}} + {{- end -}} + + {{- $opPathKeys := list "path" -}} + {{- if or (eq $patch.op "copy") (eq $patch.op "move") -}} + {{- $opPathKeys = append $opPathKeys "from" -}} + {{- end -}} + {{- $reSlice := list -}} + + {{- range $opPathKey := $opPathKeys -}} + {{- $obj := $docContainer -}} + {{- if and (eq $patch.op "copy") (eq $opPathKey "from") -}} + {{- $obj = (fromJson (toJson $docContainer)) -}} + {{- end -}} + {{- $key := "doc" -}} + {{- $lastMap := dict "root" $obj -}} + {{- $lastKey := "root" -}} + {{- $paths := (splitList "/" (get $patch $opPathKey)) -}} + {{- $firstPath := index $paths 0 -}} + {{- if ne (index $paths 0) "" -}} + {{- fail (cat "invalid" $opPathKey (get $patch $opPathKey) "must be empty string or start with /") -}} + {{- end -}} + {{- $paths = slice $paths 1 -}} + + {{- range $path := $paths -}} + {{- $path = replace "~1" "/" $path -}} + {{- $path = replace "~0" "~" $path -}} + + {{- if kindIs "slice" $obj -}} + {{- $mapObj := dict -}} + {{- range $i, $v := $obj -}} + {{- $_ := set $mapObj (toString $i) $v -}} + {{- end -}} + {{- $obj = $mapObj -}} + {{- $_ := set $lastMap $lastKey $obj -}} + {{- $reSlice = prepend $reSlice (dict "lastMap" $lastMap "lastKey" $lastKey "mapObj" $obj) -}} + {{- end -}} + + {{- if kindIs "map" $obj -}} + {{- if not (hasKey $obj $key) -}} + {{- fail (cat "key" $key "does not exist") -}} + {{- end -}} + {{- $lastKey = $key -}} + {{- $lastMap = $obj -}} + {{- $obj = index $obj $key -}} + {{- $key = $path -}} + {{- else -}} + {{- fail (cat "cannot iterate into path" $key "on type" (kindOf $obj)) -}} + {{- end -}} + {{- end -}} + + {{- $_ := set $patch (printf "%sKey" $opPathKey) $key -}} + {{- $_ := set $patch (printf "%sLastKey" $opPathKey) $lastKey -}} + {{- $_ = set $patch (printf "%sLastMap" $opPathKey) $lastMap -}} + {{- end -}} + + {{- if eq $patch.op "move" }} + {{- if and (ne $patch.path $patch.from) (hasPrefix (printf "%s/" $patch.path) (printf "%s/" $patch.from)) -}} + {{- fail (cat "from" $patch.from "may not be a child of path" $patch.path) -}} + {{- end -}} + {{- end -}} + + {{- if or (eq $patch.op "move") (eq $patch.op "copy") (eq $patch.op "test") }} + {{- $key := $patch.fromKey -}} + {{- $lastMap := $patch.fromLastMap -}} + {{- $lastKey := $patch.fromLastKey -}} + {{- $setKey := "value" -}} + {{- if eq $patch.op "test" }} + {{- $key = $patch.pathKey -}} + {{- $lastMap = $patch.pathLastMap -}} + {{- $lastKey = $patch.pathLastKey -}} + {{- $setKey = "testValue" -}} + {{- end -}} + {{- $obj := index $lastMap $lastKey -}} + + {{- if kindIs "map" $obj -}} + {{- if not (hasKey $obj $key) -}} + {{- fail (cat $key "does not exist") -}} + {{- end -}} + {{- $_ := set $patch $setKey (index $obj $key) -}} + + {{- else if kindIs "slice" $obj -}} + {{- $i := atoi $key -}} + {{- if ne $key (toString $i) -}} + {{- fail (cat "cannot convert" $key "to int") -}} + {{- end -}} + {{- if lt $i 0 -}} + {{- fail "slice index <0" -}} + {{- else if lt $i (len $obj) -}} + {{- $_ := set $patch $setKey (index $obj $i) -}} + {{- else -}} + {{- fail "slice index >= slice length" -}} + {{- end -}} + + {{- else -}} + {{- fail (cat "cannot" $patch.op $key "on type" (kindOf $obj)) -}} + {{- end -}} + {{- end -}} + + {{- if or (eq $patch.op "remove") (eq $patch.op "replace") (eq $patch.op "move") }} + {{- $key := $patch.pathKey -}} + {{- $lastMap := $patch.pathLastMap -}} + {{- $lastKey := $patch.pathLastKey -}} + {{- if eq $patch.op "move" }} + {{- $key = $patch.fromKey -}} + {{- $lastMap = $patch.fromLastMap -}} + {{- $lastKey = $patch.fromLastKey -}} + {{- end -}} + {{- $obj := index $lastMap $lastKey -}} + + {{- if kindIs "map" $obj -}} + {{- if not (hasKey $obj $key) -}} + {{- fail (cat $key "does not exist") -}} + {{- end -}} + {{- $_ := unset $obj $key -}} + + {{- else if kindIs "slice" $obj -}} + {{- $i := atoi $key -}} + {{- if ne $key (toString $i) -}} + {{- fail (cat "cannot convert" $key "to int") -}} + {{- end -}} + {{- if lt $i 0 -}} + {{- fail "slice index <0" -}} + {{- else if eq $i 0 -}} + {{- $_ := set $lastMap $lastKey (slice $obj 1) -}} + {{- else if lt $i (sub (len $obj) 1) -}} + {{- $_ := set $lastMap $lastKey (concat (slice $obj 0 $i) (slice $obj (add $i 1) (len $obj))) -}} + {{- else if eq $i (sub (len $obj) 1) -}} + {{- $_ := set $lastMap $lastKey (slice $obj 0 (sub (len $obj) 1)) -}} + {{- else -}} + {{- fail "slice index >= slice length" -}} + {{- end -}} + + {{- else -}} + {{- fail (cat "cannot" $patch.op $key "on type" (kindOf $obj)) -}} + {{- end -}} + {{- end -}} + + {{- if or (eq $patch.op "add") (eq $patch.op "replace") (eq $patch.op "move") (eq $patch.op "copy") }} + {{- $key := $patch.pathKey -}} + {{- $lastMap := $patch.pathLastMap -}} + {{- $lastKey := $patch.pathLastKey -}} + {{- $value := $patch.value -}} + {{- $obj := index $lastMap $lastKey -}} + + {{- if kindIs "map" $obj -}} + {{- $_ := set $obj $key $value -}} + + {{- else if kindIs "slice" $obj -}} + {{- $i := 0 -}} + {{- if eq $key "-" -}} + {{- $i = len $obj -}} + {{- else -}} + {{- $i = atoi $key -}} + {{- if ne $key (toString $i) -}} + {{- fail (cat "cannot convert" $key "to int") -}} + {{- end -}} + {{- end -}} + {{- if lt $i 0 -}} + {{- fail "slice index <0" -}} + {{- else if eq $i 0 -}} + {{- $_ := set $lastMap $lastKey (prepend $obj $value) -}} + {{- else if lt $i (len $obj) -}} + {{- $_ := set $lastMap $lastKey (concat (append (slice $obj 0 $i) $value) (slice $obj $i)) -}} + {{- else if eq $i (len $obj) -}} + {{- $_ := set $lastMap $lastKey (append $obj $value) -}} + {{- else -}} + {{- fail "slice index > slice length" -}} + {{- end -}} + + {{- else -}} + {{- fail (cat "cannot" $patch.op $key "on type" (kindOf $obj)) -}} + {{- end -}} + {{- end -}} + + {{- if eq $patch.op "test" }} + {{- if not (deepEqual $patch.value $patch.testValue) }} + {{- fail (cat "test failed, expected" (toJson $patch.value) "but got" (toJson $patch.testValue)) -}} + {{- end -}} + {{- end -}} + + {{- range $reSliceOp := $reSlice -}} + {{- $sliceObj := list -}} + {{- range $i := until (len $reSliceOp.mapObj) -}} + {{- $sliceObj = append $sliceObj (index $reSliceOp.mapObj (toString $i)) -}} + {{- end -}} + {{- $_ := set $reSliceOp.lastMap $reSliceOp.lastKey $sliceObj -}} + {{- end -}} + + {{- end -}} + {{- toJson $docContainer -}} +{{- end -}} diff --git a/charts/nats/nats/1.2.3/templates/_toPrettyRawJson.tpl b/charts/nats/nats/1.2.3/templates/_toPrettyRawJson.tpl new file mode 100644 index 0000000000..612a62f9c2 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/_toPrettyRawJson.tpl @@ -0,0 +1,28 @@ +{{- /* +toPrettyRawJson +input: interface{} valid JSON document +output: pretty raw JSON string +*/}} +{{- define "toPrettyRawJson" -}} + {{- include "toPrettyRawJsonStr" (toPrettyJson .) -}} +{{- end -}} + +{{- /* +toPrettyRawJsonStr +input: pretty JSON string +output: pretty raw JSON string +*/}} +{{- define "toPrettyRawJsonStr" -}} + {{- $s := + (regexReplaceAll "([^\\\\](?:\\\\\\\\)*)\\\\u003e" + (regexReplaceAll "([^\\\\](?:\\\\\\\\)*)\\\\u003c" + (regexReplaceAll "([^\\\\](?:\\\\\\\\)*)\\\\u0026" . "${1}&") + "${1}<") + "${1}>") + -}} + {{- if regexMatch "([^\\\\](?:\\\\\\\\)*)\\\\u00(26|3c|3e)" $s -}} + {{- include "toPrettyRawJsonStr" $s -}} + {{- else -}} + {{- $s -}} + {{- end -}} +{{- end -}} diff --git a/charts/nats/nats/1.2.3/templates/_tplYaml.tpl b/charts/nats/nats/1.2.3/templates/_tplYaml.tpl new file mode 100644 index 0000000000..f42b9c1681 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/_tplYaml.tpl @@ -0,0 +1,114 @@ +{{- /* +tplYaml +input: map with 2 keys: +- doc: interface{} +- ctx: context to pass to tpl function +output: JSON encoded map with 1 key: +- doc: interface{} with any keys called tpl or tplSpread values templated and replaced + +maps matching the following syntax will be templated and parsed as YAML +{ + $tplYaml: string +} + +maps matching the follow syntax will be templated, parsed as YAML, and spread into the parent map/slice +{ + $tplYamlSpread: string +} +*/}} +{{- define "tplYaml" -}} + {{- $patch := get (include "tplYamlItr" (dict "ctx" .ctx "parentKind" "" "parentPath" "" "path" "/" "value" .doc) | fromJson) "patch" -}} + {{- include "jsonpatch" (dict "doc" .doc "patch" $patch) -}} +{{- end -}} + +{{- /* +tplYamlItr +input: map with 4 keys: +- path: string JSONPath to current element +- parentKind: string kind of parent element +- parentPath: string JSONPath to parent element +- value: interface{} +- ctx: context to pass to tpl function +output: JSON encoded map with 1 key: +- patch: list of patches to apply in order to template +*/}} +{{- define "tplYamlItr" -}} + {{- $params := . -}} + {{- $kind := kindOf $params.value -}} + {{- $patch := list -}} + {{- $joinPath := $params.path -}} + {{- if eq $params.path "/" -}} + {{- $joinPath = "" -}} + {{- end -}} + {{- $joinParentPath := $params.parentPath -}} + {{- if eq $params.parentPath "/" -}} + {{- $joinParentPath = "" -}} + {{- end -}} + + {{- if eq $kind "slice" -}} + {{- $iAdj := 0 -}} + {{- range $i, $v := $params.value -}} + {{- $iPath := printf "%s/%d" $joinPath (add $i $iAdj) -}} + {{- $itrPatch := get (include "tplYamlItr" (dict "ctx" $params.ctx "parentKind" $kind "parentPath" $params.path "path" $iPath "value" $v) | fromJson) "patch" -}} + {{- $itrLen := len $itrPatch -}} + {{- if gt $itrLen 0 -}} + {{- $patch = concat $patch $itrPatch -}} + {{- if eq (get (index $itrPatch 0) "op") "remove" -}} + {{- $iAdj = add $iAdj (sub $itrLen 2) -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- else if eq $kind "map" -}} + {{- if and (eq (len $params.value) 1) (or (hasKey $params.value "$tplYaml") (hasKey $params.value "$tplYamlSpread")) -}} + {{- $tpl := get $params.value "$tplYaml" -}} + {{- $spread := false -}} + {{- if hasKey $params.value "$tplYamlSpread" -}} + {{- if eq $params.path "/" -}} + {{- fail "cannot $tplYamlSpread on root object" -}} + {{- end -}} + {{- $tpl = get $params.value "$tplYamlSpread" -}} + {{- $spread = true -}} + {{- end -}} + + {{- $res := tpl $tpl $params.ctx -}} + {{- $res = get (fromYaml (tpl "tpl: {{ nindent 2 .res }}" (merge (dict "res" $res) $params.ctx))) "tpl" -}} + + {{- if eq $spread false -}} + {{- $patch = append $patch (dict "op" "replace" "path" $params.path "value" $res) -}} + {{- else -}} + {{- $resKind := kindOf $res -}} + {{- if and (ne $resKind "invalid") (ne $resKind $params.parentKind) -}} + {{- fail (cat "can only $tplYamlSpread slice onto a slice or map onto a map; attempted to spread" $resKind "on" $params.parentKind "at path" $params.path) -}} + {{- end -}} + {{- $patch = append $patch (dict "op" "remove" "path" $params.path) -}} + {{- if eq $resKind "invalid" -}} + {{- /* no-op */ -}} + {{- else if eq $resKind "slice" -}} + {{- range $v := reverse $res -}} + {{- $patch = append $patch (dict "op" "add" "path" $params.path "value" $v) -}} + {{- end -}} + {{- else -}} + {{- range $k, $v := $res -}} + {{- $kPath := replace "~" "~0" $k -}} + {{- $kPath = replace "/" "~1" $kPath -}} + {{- $kPath = printf "%s/%s" $joinParentPath $kPath -}} + {{- $patch = append $patch (dict "op" "add" "path" $kPath "value" $v) -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- else -}} + {{- range $k, $v := $params.value -}} + {{- $kPath := replace "~" "~0" $k -}} + {{- $kPath = replace "/" "~1" $kPath -}} + {{- $kPath = printf "%s/%s" $joinPath $kPath -}} + {{- $itrPatch := get (include "tplYamlItr" (dict "ctx" $params.ctx "parentKind" $kind "parentPath" $params.path "path" $kPath "value" $v) | fromJson) "patch" -}} + {{- if gt (len $itrPatch) 0 -}} + {{- $patch = concat $patch $itrPatch -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- toJson (dict "patch" $patch) -}} +{{- end -}} diff --git a/charts/nats/nats/1.2.3/templates/config-map.yaml b/charts/nats/nats/1.2.3/templates/config-map.yaml new file mode 100644 index 0000000000..b95afda205 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/config-map.yaml @@ -0,0 +1,4 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.configMap }} +{{- include "nats.loadMergePatch" (merge (dict "file" "config-map.yaml" "ctx" $) .) }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/extra-resources.yaml b/charts/nats/nats/1.2.3/templates/extra-resources.yaml new file mode 100644 index 0000000000..c11f0085e3 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/extra-resources.yaml @@ -0,0 +1,5 @@ +{{- include "nats.defaultValues" . }} +{{- range .Values.extraResources }} +--- +{{ . | toYaml }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/headless-service.yaml b/charts/nats/nats/1.2.3/templates/headless-service.yaml new file mode 100644 index 0000000000..f11a83d130 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/headless-service.yaml @@ -0,0 +1,4 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.headlessService }} +{{- include "nats.loadMergePatch" (merge (dict "file" "headless-service.yaml" "ctx" $) .) }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/ingress.yaml b/charts/nats/nats/1.2.3/templates/ingress.yaml new file mode 100644 index 0000000000..eccd73ffdb --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/ingress.yaml @@ -0,0 +1,6 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.config.websocket.ingress }} +{{- if and .enabled .hosts $.Values.config.websocket.enabled $.Values.service.enabled $.Values.service.ports.websocket.enabled }} +{{- include "nats.loadMergePatch" (merge (dict "file" "ingress.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/nats-box/contents-secret.yaml b/charts/nats/nats/1.2.3/templates/nats-box/contents-secret.yaml new file mode 100644 index 0000000000..db629bf7b1 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/nats-box/contents-secret.yaml @@ -0,0 +1,10 @@ +{{- include "nats.defaultValues" . }} +{{- if .hasContentsSecret }} +{{- with .Values.natsBox }} +{{- if .enabled }} +{{- with .contentsSecret}} +{{- include "nats.loadMergePatch" (merge (dict "file" "nats-box/contents-secret.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/nats-box/contexts-secret.yaml b/charts/nats/nats/1.2.3/templates/nats-box/contexts-secret.yaml new file mode 100644 index 0000000000..5ae20f45a5 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/nats-box/contexts-secret.yaml @@ -0,0 +1,8 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.natsBox }} +{{- if .enabled }} +{{- with .contextsSecret}} +{{- include "nats.loadMergePatch" (merge (dict "file" "nats-box/contexts-secret/contexts-secret.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/nats-box/deployment.yaml b/charts/nats/nats/1.2.3/templates/nats-box/deployment.yaml new file mode 100644 index 0000000000..a063332a21 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/nats-box/deployment.yaml @@ -0,0 +1,8 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.natsBox }} +{{- if .enabled }} +{{- with .deployment }} +{{- include "nats.loadMergePatch" (merge (dict "file" "nats-box/deployment/deployment.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/nats-box/service-account.yaml b/charts/nats/nats/1.2.3/templates/nats-box/service-account.yaml new file mode 100644 index 0000000000..e11bdd3635 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/nats-box/service-account.yaml @@ -0,0 +1,8 @@ +{{- include "nats.defaultValues" . }} +{{- if .Values.natsBox.enabled }} +{{- with .Values.natsBox.serviceAccount }} +{{- if .enabled }} +{{- include "nats.loadMergePatch" (merge (dict "file" "nats-box/service-account.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/pod-disruption-budget.yaml b/charts/nats/nats/1.2.3/templates/pod-disruption-budget.yaml new file mode 100644 index 0000000000..911722629d --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/pod-disruption-budget.yaml @@ -0,0 +1,6 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.podDisruptionBudget }} +{{- if .enabled }} +{{- include "nats.loadMergePatch" (merge (dict "file" "pod-disruption-budget.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/pod-monitor.yaml b/charts/nats/nats/1.2.3/templates/pod-monitor.yaml new file mode 100644 index 0000000000..0e42a43a55 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/pod-monitor.yaml @@ -0,0 +1,8 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.promExporter }} +{{- if and .enabled .podMonitor.enabled }} +{{- with .podMonitor }} +{{- include "nats.loadMergePatch" (merge (dict "file" "pod-monitor.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/service-account.yaml b/charts/nats/nats/1.2.3/templates/service-account.yaml new file mode 100644 index 0000000000..6c763bd3e6 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/service-account.yaml @@ -0,0 +1,6 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.serviceAccount }} +{{- if .enabled }} +{{- include "nats.loadMergePatch" (merge (dict "file" "service-account.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/service.yaml b/charts/nats/nats/1.2.3/templates/service.yaml new file mode 100644 index 0000000000..04b0b37e75 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/service.yaml @@ -0,0 +1,6 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.service }} +{{- if .enabled }} +{{- include "nats.loadMergePatch" (merge (dict "file" "service.yaml" "ctx" $) .) }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/stateful-set.yaml b/charts/nats/nats/1.2.3/templates/stateful-set.yaml new file mode 100644 index 0000000000..bb198323e4 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/stateful-set.yaml @@ -0,0 +1,4 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.statefulSet }} +{{- include "nats.loadMergePatch" (merge (dict "file" "stateful-set/stateful-set.yaml" "ctx" $) .) }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/templates/tests/request-reply.yaml b/charts/nats/nats/1.2.3/templates/tests/request-reply.yaml new file mode 100644 index 0000000000..3e06edc082 --- /dev/null +++ b/charts/nats/nats/1.2.3/templates/tests/request-reply.yaml @@ -0,0 +1,37 @@ +{{- include "nats.defaultValues" . }} +{{- with .Values.natsBox | deepCopy }} +{{- $natsBox := . }} +{{- if .enabled -}} +apiVersion: v1 +kind: Pod +{{- with .container }} +{{- $_ := set . "merge" (dict + "args" (list + "sh" + "-ec" + "nats reply --echo echo & pid=\"$!\"; sleep 1; nats request echo hi > /tmp/resp; kill \"$pid\"; wait; grep -qF hi /tmp/resp" + ) +) }} +{{- $_ := set . "patch" list }} +{{- end }} +{{- with .podTemplate }} +{{- $_ := set . "merge" (dict + "metadata" (dict + "name" (printf "%s-test-request-reply" $.Values.statefulSet.name) + "labels" (dict + "app.kubernetes.io/component" "test-request-reply" + ) + "annotations" (dict + "helm.sh/hook" "test" + "helm.sh/hook-delete-policy" "before-hook-creation,hook-succeeded" + ) + ) + "spec" (dict + "restartPolicy" "Never" + ) +) }} +{{- $_ := set . "patch" list }} +{{ include "nats.loadMergePatch" (merge (dict "file" "nats-box/deployment/pod-template.yaml" "ctx" (merge (dict "Values" (dict "natsBox" $natsBox)) $)) .) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/nats/nats/1.2.3/values.yaml b/charts/nats/nats/1.2.3/values.yaml new file mode 100644 index 0000000000..fd296dcc4b --- /dev/null +++ b/charts/nats/nats/1.2.3/values.yaml @@ -0,0 +1,669 @@ +################################################################################ +# Global options +################################################################################ +global: + image: + # global image pull policy to use for all container images in the chart + # can be overridden by individual image pullPolicy + pullPolicy: + # global list of secret names to use as image pull secrets for all pod specs in the chart + # secrets must exist in the same namespace + # https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + pullSecretNames: [] + # global registry to use for all container images in the chart + # can be overridden by individual image registry + registry: + + # global labels will be applied to all resources deployed by the chart + labels: {} + +################################################################################ +# Common options +################################################################################ +# override name of the chart +nameOverride: +# override full name of the chart+release +fullnameOverride: +# override the namespace that resources are installed into +namespaceOverride: + +# reference a common CA Certificate or Bundle in all nats config `tls` blocks and nats-box contexts +# note: `tls.verify` still must be set in the appropriate nats config `tls` blocks to require mTLS +tlsCA: + enabled: false + # set configMapName in order to mount an existing configMap to dir + configMapName: + # set secretName in order to mount an existing secretName to dir + secretName: + # directory to mount the configMap or secret to + dir: /etc/nats-ca-cert + # key in the configMap or secret that contains the CA Certificate or Bundle + key: ca.crt + +################################################################################ +# NATS Stateful Set and associated resources +################################################################################ + +############################################################ +# NATS config +############################################################ +config: + cluster: + enabled: false + port: 6222 + # must be 2 or higher when jetstream is enabled + replicas: 3 + + # apply to generated route URLs that connect to other pods in the StatefulSet + routeURLs: + # if both user and password are set, they will be added to route URLs + # and the cluster authorization block + user: + password: + # set to true to use FQDN in route URLs + useFQDN: false + k8sClusterDomain: cluster.local + + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/cluster + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + # merge or patch the cluster config + # https://docs.nats.io/running-a-nats-service/configuration/clustering/cluster_config + merge: {} + patch: [] + + jetstream: + enabled: false + + fileStore: + enabled: true + dir: /data + + ############################################################ + # stateful set -> volume claim templates -> jetstream pvc + ############################################################ + pvc: + enabled: true + size: 10Gi + storageClassName: + + # merge or patch the jetstream pvc + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#persistentvolumeclaim-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-js" + name: + + # defaults to the PVC size + maxSize: + + memoryStore: + enabled: false + # ensure that container has a sufficient memory limit greater than maxSize + maxSize: 1Gi + + # merge or patch the jetstream config + # https://docs.nats.io/running-a-nats-service/configuration#jetstream + merge: {} + patch: [] + + nats: + port: 4222 + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/nats + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + leafnodes: + enabled: false + port: 7422 + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/leafnodes + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + # merge or patch the leafnodes config + # https://docs.nats.io/running-a-nats-service/configuration/leafnodes/leafnode_conf + merge: {} + patch: [] + + websocket: + enabled: false + port: 8080 + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/websocket + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + ############################################################ + # ingress + ############################################################ + # service must be enabled also + ingress: + enabled: false + # must contain at least 1 host otherwise ingress will not be created + hosts: [] + path: / + pathType: Exact + # sets to the ingress class name + className: + # set to an existing secret name to enable TLS on the ingress; applies to all hosts + tlsSecretName: + + # merge or patch the ingress + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#ingress-v1-networking-k8s-io + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-ws" + name: + + # merge or patch the websocket config + # https://docs.nats.io/running-a-nats-service/configuration/websocket/websocket_conf + merge: {} + patch: [] + + mqtt: + enabled: false + port: 1883 + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/mqtt + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + # merge or patch the mqtt config + # https://docs.nats.io/running-a-nats-service/configuration/mqtt/mqtt_config + merge: {} + patch: [] + + gateway: + enabled: false + port: 7222 + tls: + enabled: false + # set secretName in order to mount an existing secret to dir + secretName: + dir: /etc/nats-certs/gateway + cert: tls.crt + key: tls.key + # merge or patch the tls config + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls + merge: {} + patch: [] + + # merge or patch the gateway config + # https://docs.nats.io/running-a-nats-service/configuration/gateways/gateway#gateway-configuration-block + merge: {} + patch: [] + + monitor: + enabled: true + port: 8222 + tls: + # config.nats.tls must be enabled also + # when enabled, monitoring port will use HTTPS with the options from config.nats.tls + enabled: false + + profiling: + enabled: false + port: 65432 + + resolver: + enabled: false + dir: /data/resolver + + ############################################################ + # stateful set -> volume claim templates -> resolver pvc + ############################################################ + pvc: + enabled: true + size: 1Gi + storageClassName: + + # merge or patch the pvc + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#persistentvolumeclaim-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-resolver" + name: + + # merge or patch the resolver + # https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/jwt/resolver + merge: {} + patch: [] + + # adds a prefix to the server name, which defaults to the pod name + # helpful for ensuring server name is unique in a super cluster + serverNamePrefix: "" + + # merge or patch the nats config + # https://docs.nats.io/running-a-nats-service/configuration + # following special rules apply + # 1. strings that start with << and end with >> will be unquoted + # use this for variables and numbers with units + # 2. keys ending in $include will be switched to include directives + # keys are sorted alphabetically, use prefix before $includes to control includes ordering + # paths should be relative to /etc/nats-config/nats.conf + # example: + # + # merge: + # $include: ./my-config.conf + # zzz$include: ./my-config-last.conf + # server_name: nats + # authorization: + # token: << $TOKEN >> + # jetstream: + # max_memory_store: << 1GB >> + # + # will yield the config: + # { + # include ./my-config.conf; + # "authorization": { + # "token": $TOKEN + # }, + # "jetstream": { + # "max_memory_store": 1GB + # }, + # "server_name": "nats", + # include ./my-config-last.conf; + # } + merge: {} + patch: [] + +############################################################ +# stateful set -> pod template -> nats container +############################################################ +container: + image: + repository: nats + tag: 2.10.19-alpine + pullPolicy: + registry: + + # container port options + # must be enabled in the config section also + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#containerport-v1-core + ports: + nats: {} + leafnodes: {} + websocket: {} + mqtt: {} + cluster: {} + gateway: {} + monitor: {} + profiling: {} + + # map with key as env var name, value can be string or map + # example: + # + # env: + # GOMEMLIMIT: 7GiB + # TOKEN: + # valueFrom: + # secretKeyRef: + # name: nats-auth + # key: token + env: {} + + # merge or patch the container + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core + merge: {} + patch: [] + +############################################################ +# stateful set -> pod template -> reloader container +############################################################ +reloader: + enabled: true + image: + repository: natsio/nats-server-config-reloader + tag: 0.15.1 + pullPolicy: + registry: + + # env var map, see nats.env for an example + env: {} + + # all nats container volume mounts with the following prefixes + # will be mounted into the reloader container + natsVolumeMountPrefixes: + - /etc/ + + # merge or patch the container + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core + merge: {} + patch: [] + +############################################################ +# stateful set -> pod template -> prom-exporter container +############################################################ +# config.monitor must be enabled +promExporter: + enabled: false + image: + repository: natsio/prometheus-nats-exporter + tag: 0.15.0 + pullPolicy: + registry: + + port: 7777 + # env var map, see nats.env for an example + env: {} + + # merge or patch the container + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core + merge: {} + patch: [] + + ############################################################ + # prometheus pod monitor + ############################################################ + podMonitor: + enabled: false + + # merge or patch the pod monitor + # https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}" + name: + + +############################################################ +# service +############################################################ +service: + enabled: true + + # service port options + # additional boolean field enable to control whether port is exposed in the service + # must be enabled in the config section also + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#serviceport-v1-core + ports: + nats: + enabled: true + leafnodes: + enabled: true + websocket: + enabled: true + mqtt: + enabled: true + cluster: + enabled: false + gateway: + enabled: false + monitor: + enabled: false + profiling: + enabled: false + + # merge or patch the service + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#service-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}" + name: + +############################################################ +# other nats extension points +############################################################ + +# stateful set +statefulSet: + # merge or patch the stateful set + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#statefulset-v1-apps + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}" + name: + +# stateful set -> pod template +podTemplate: + # adds a hash of the ConfigMap as a pod annotation + # this will cause the StatefulSet to roll when the ConfigMap is updated + configChecksumAnnotation: true + + # map of topologyKey: topologySpreadConstraint + # labelSelector will be added to match StatefulSet pods + # + # topologySpreadConstraints: + # kubernetes.io/hostname: + # maxSkew: 1 + # + topologySpreadConstraints: {} + + # merge or patch the pod template + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#pod-v1-core + merge: {} + patch: [] + +# headless service +headlessService: + # merge or patch the headless service + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#service-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-headless" + name: + +# config map +configMap: + # merge or patch the config map + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#configmap-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-config" + name: + +# pod disruption budget +podDisruptionBudget: + enabled: true + # merge or patch the pod disruption budget + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#poddisruptionbudget-v1-policy + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}" + name: + +# service account +serviceAccount: + enabled: false + # merge or patch the service account + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#serviceaccount-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}" + name: + + +############################################################ +# natsBox +# +# NATS Box Deployment and associated resources +############################################################ +natsBox: + enabled: true + + ############################################################ + # NATS contexts + ############################################################ + contexts: + default: + creds: + # set contents in order to create a secret with the creds file contents + contents: + # set secretName in order to mount an existing secret to dir + secretName: + # defaults to /etc/nats-creds/ + dir: + key: nats.creds + nkey: + # set contents in order to create a secret with the nkey file contents + contents: + # set secretName in order to mount an existing secret to dir + secretName: + # defaults to /etc/nats-nkeys/ + dir: + key: nats.nk + # used to connect with client certificates + tls: + # set secretName in order to mount an existing secret to dir + secretName: + # defaults to /etc/nats-certs/ + dir: + cert: tls.crt + key: tls.key + + # merge or patch the context + # https://docs.nats.io/using-nats/nats-tools/nats_cli#nats-contexts + merge: {} + patch: [] + + # name of context to select by default + defaultContextName: default + + ############################################################ + # deployment -> pod template -> nats-box container + ############################################################ + container: + image: + repository: natsio/nats-box + tag: 0.14.4 + pullPolicy: + registry: + + # env var map, see nats.env for an example + env: {} + + # merge or patch the container + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core + merge: {} + patch: [] + + ############################################################ + # other nats-box extension points + ############################################################ + + # deployment + deployment: + # merge or patch the deployment + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#deployment-v1-apps + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-box" + name: + + # deployment -> pod template + podTemplate: + # merge or patch the pod template + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#pod-v1-core + merge: {} + patch: [] + + # contexts secret + contextsSecret: + # merge or patch the context secret + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#secret-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-box-contexts" + name: + + # contents secret + contentsSecret: + # merge or patch the contents secret + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#secret-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-box-contents" + name: + + # service account + serviceAccount: + enabled: false + # merge or patch the service account + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#serviceaccount-v1-core + merge: {} + patch: [] + # defaults to "{{ include "nats.fullname" $ }}-box" + name: + + +################################################################################ +# Extra user-defined resources +################################################################################ +# +# add arbitrary user-generated resources +# example: +# +# config: +# websocket: +# enabled: true +# extraResources: +# - apiVersion: networking.istio.io/v1beta1 +# kind: VirtualService +# metadata: +# name: +# $tplYaml: > +# {{ include "nats.fullname" $ | quote }} +# labels: +# $tplYaml: | +# {{ include "nats.labels" $ }} +# spec: +# hosts: +# - demo.nats.io +# gateways: +# - my-gateway +# http: +# - name: default +# match: +# - name: root +# uri: +# exact: / +# route: +# - destination: +# host: +# $tplYaml: > +# {{ .Values.service.name | quote }} +# port: +# number: +# $tplYaml: > +# {{ .Values.config.websocket.port }} +# +extraResources: [] diff --git a/charts/speedscale/speedscale-operator/2.2.314/.helmignore b/charts/speedscale/speedscale-operator/2.2.314/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/speedscale/speedscale-operator/2.2.314/Chart.yaml b/charts/speedscale/speedscale-operator/2.2.314/Chart.yaml new file mode 100644 index 0000000000..2f28517f4a --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/Chart.yaml @@ -0,0 +1,27 @@ +annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Speedscale Operator + catalog.cattle.io/kube-version: '>= 1.17.0-0' + catalog.cattle.io/release-name: speedscale-operator +apiVersion: v1 +appVersion: 2.2.314 +description: Stress test your APIs with real world scenarios. Collect and replay + traffic without scripting. +home: https://speedscale.com +icon: file://assets/icons/speedscale-operator.png +keywords: +- speedscale +- test +- testing +- regression +- reliability +- load +- replay +- network +- traffic +kubeVersion: '>= 1.17.0-0' +maintainers: +- email: support@speedscale.com + name: Speedscale Support +name: speedscale-operator +version: 2.2.314 diff --git a/charts/speedscale/speedscale-operator/2.2.314/LICENSE b/charts/speedscale/speedscale-operator/2.2.314/LICENSE new file mode 100644 index 0000000000..b78723d62f --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/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 2021 Speedscale + + 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/charts/speedscale/speedscale-operator/2.2.314/README.md b/charts/speedscale/speedscale-operator/2.2.314/README.md new file mode 100644 index 0000000000..6ca25eed9d --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/README.md @@ -0,0 +1,111 @@ +![GitHub Tag](https://img.shields.io/github/v/tag/speedscale/operator-helm) + + +# Speedscale Operator + +The [Speedscale](https://www.speedscale.com) Operator is a [Kubernetes operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) +that watches for deployments to be applied to the cluster and takes action based on annotations. The operator +can inject a proxy to capture traffic into or out of applications, or setup an isolation test environment around +a deployment for testing. The operator itself is a deployment that will be always present on the cluster once +the helm chart is installed. + +## Prerequisites + +- Kubernetes 1.20+ +- Helm 3+ +- Appropriate [network and firewall configuration](https://docs.speedscale.com/reference/networking) for Speedscale cloud and webhook traffic + +## Get Repo Info + +```bash +helm repo add speedscale https://speedscale.github.io/operator-helm/ +helm repo update +``` + +_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._ + +## Install Chart + +An API key is required. Sign up for a [free Speedscale trial](https://speedscale.com/free-trial/) if you do not have one. + +```bash +helm install speedscale-operator speedscale/speedscale-operator \ + -n speedscale \ + --create-namespace \ + --set apiKey= \ + --set clusterName= +``` + +_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._ + +### Pre-install job failure + +We use pre-install job to check provided API key and provision some of the required resources. + +If the job failed during the installation, you'll see the following error during install: + +``` +Error: INSTALLATION FAILED: failed pre-install: job failed: BackoffLimitExceeded +``` + +You can inspect the logs using this command: + +```bash +kubectl -n speedscale logs job/speedscale-operator-pre-install +``` + +After fixing the error, uninstall the helm release, delete the failed job +and try installing again: + +```bash +helm -n speedscale uninstall speedscale-operator +kubectl -n speedscale delete job speedscale-operator-pre-install +``` + +## Uninstall Chart + +```bash +helm -n speedscale uninstall speedscale-operator +``` + +This removes all the Kubernetes components associated with the chart and deletes the release. + +_See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation._ + +CRDs created by this chart are not removed by default and should be manually cleaned up: + +```bash +kubectl delete crd trafficreplays.speedscale.com +``` + +## Upgrading Chart + +```bash +helm repo update +helm -n speedscale upgrade speedscale-operator speedscale/speedscale-operator +``` + +Resources capturing traffic will need to be rolled to pick up the latest +Speedscale sidecar. Use the rollout restart command for each namespace and +resource type: + +```bash +kubectl -n rollout restart deployment +``` + +With Helm v3, CRDs created by this chart are not updated by default +and should be manually updated. +Consult also the [Helm Documentation on CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions). + +_See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation._ + +### Upgrading an existing Release to a new version + +A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an +incompatible breaking change needing manual actions. + + +## Help + +Speedscale docs information available at [docs.speedscale.com](https://docs.speedscale.com) or join us +on the [Speedscale community Slack](https://join.slack.com/t/speedscalecommunity/shared_invite/zt-x5rcrzn4-XHG1QqcHNXIM~4yozRrz8A)! diff --git a/charts/speedscale/speedscale-operator/2.2.314/app-readme.md b/charts/speedscale/speedscale-operator/2.2.314/app-readme.md new file mode 100644 index 0000000000..6ca25eed9d --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/app-readme.md @@ -0,0 +1,111 @@ +![GitHub Tag](https://img.shields.io/github/v/tag/speedscale/operator-helm) + + +# Speedscale Operator + +The [Speedscale](https://www.speedscale.com) Operator is a [Kubernetes operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) +that watches for deployments to be applied to the cluster and takes action based on annotations. The operator +can inject a proxy to capture traffic into or out of applications, or setup an isolation test environment around +a deployment for testing. The operator itself is a deployment that will be always present on the cluster once +the helm chart is installed. + +## Prerequisites + +- Kubernetes 1.20+ +- Helm 3+ +- Appropriate [network and firewall configuration](https://docs.speedscale.com/reference/networking) for Speedscale cloud and webhook traffic + +## Get Repo Info + +```bash +helm repo add speedscale https://speedscale.github.io/operator-helm/ +helm repo update +``` + +_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._ + +## Install Chart + +An API key is required. Sign up for a [free Speedscale trial](https://speedscale.com/free-trial/) if you do not have one. + +```bash +helm install speedscale-operator speedscale/speedscale-operator \ + -n speedscale \ + --create-namespace \ + --set apiKey= \ + --set clusterName= +``` + +_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._ + +### Pre-install job failure + +We use pre-install job to check provided API key and provision some of the required resources. + +If the job failed during the installation, you'll see the following error during install: + +``` +Error: INSTALLATION FAILED: failed pre-install: job failed: BackoffLimitExceeded +``` + +You can inspect the logs using this command: + +```bash +kubectl -n speedscale logs job/speedscale-operator-pre-install +``` + +After fixing the error, uninstall the helm release, delete the failed job +and try installing again: + +```bash +helm -n speedscale uninstall speedscale-operator +kubectl -n speedscale delete job speedscale-operator-pre-install +``` + +## Uninstall Chart + +```bash +helm -n speedscale uninstall speedscale-operator +``` + +This removes all the Kubernetes components associated with the chart and deletes the release. + +_See [helm uninstall](https://helm.sh/docs/helm/helm_uninstall/) for command documentation._ + +CRDs created by this chart are not removed by default and should be manually cleaned up: + +```bash +kubectl delete crd trafficreplays.speedscale.com +``` + +## Upgrading Chart + +```bash +helm repo update +helm -n speedscale upgrade speedscale-operator speedscale/speedscale-operator +``` + +Resources capturing traffic will need to be rolled to pick up the latest +Speedscale sidecar. Use the rollout restart command for each namespace and +resource type: + +```bash +kubectl -n rollout restart deployment +``` + +With Helm v3, CRDs created by this chart are not updated by default +and should be manually updated. +Consult also the [Helm Documentation on CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions). + +_See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation._ + +### Upgrading an existing Release to a new version + +A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an +incompatible breaking change needing manual actions. + + +## Help + +Speedscale docs information available at [docs.speedscale.com](https://docs.speedscale.com) or join us +on the [Speedscale community Slack](https://join.slack.com/t/speedscalecommunity/shared_invite/zt-x5rcrzn4-XHG1QqcHNXIM~4yozRrz8A)! diff --git a/charts/speedscale/speedscale-operator/2.2.314/questions.yaml b/charts/speedscale/speedscale-operator/2.2.314/questions.yaml new file mode 100644 index 0000000000..29aee38958 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/questions.yaml @@ -0,0 +1,9 @@ +questions: +- variable: apiKey + default: "fffffffffffffffffffffffffffffffffffffffffffff" + description: "An API key is required to connect to the Speedscale cloud." + required: true + type: string + label: API Key + group: Authentication + diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/NOTES.txt b/charts/speedscale/speedscale-operator/2.2.314/templates/NOTES.txt new file mode 100644 index 0000000000..cabb59b175 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/NOTES.txt @@ -0,0 +1,12 @@ +Thank you for installing the Speedscale Operator! + +Next you'll need to add the Speedscale Proxy Sidecar to your deployments. +See https://docs.speedscale.com/setup/sidecar/install/ + +If upgrading use the rollout restart command for each namespace and resource +type to ensure Speedscale sidecars are updated: + + kubectl -n rollout restart deployment + +Once your deployment is running the sidecar your service will show up on +https://app.speedscale.com/. diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/admission.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/admission.yaml new file mode 100644 index 0000000000..9a2a943d34 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/admission.yaml @@ -0,0 +1,199 @@ +{{- $cacrt := "" -}} +{{- $crt := "" -}} +{{- $key := "" -}} +{{- $s := (lookup "v1" "Secret" .Release.Namespace "speedscale-webhook-certs") -}} +{{- if $s -}} +{{- $cacrt = index $s.data "ca.crt" | default (index $s.data "tls.crt") | b64dec -}} +{{- $crt = index $s.data "tls.crt" | b64dec -}} +{{- $key = index $s.data "tls.key" | b64dec -}} +{{ else }} +{{- $altNames := list ( printf "speedscale-operator.%s" .Release.Namespace ) ( printf "speedscale-operator.%s.svc" .Release.Namespace ) -}} +{{- $ca := genCA "speedscale-operator" 3650 -}} +{{- $cert := genSignedCert "speedscale-operator" nil $altNames 3650 $ca -}} +{{- $cacrt = $ca.Cert -}} +{{- $crt = $cert.Cert -}} +{{- $key = $cert.Key -}} +{{- end -}} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /mutate + failurePolicy: Ignore + name: sidecar.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + reinvocationPolicy: IfNeeded + rules: + - apiGroups: + - apps + - batch + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - deployments + - statefulsets + - daemonsets + - jobs + - replicasets + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - pods + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator-replay + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /mutate-speedscale-com-v1-trafficreplay + failurePolicy: Fail + name: replay.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + rules: + - apiGroups: + - speedscale.com + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - trafficreplays + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: speedscale-operator-replay + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $cacrt | b64enc }} + service: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + path: /validate-speedscale-com-v1-trafficreplay + failurePolicy: Fail + name: replay.speedscale.com + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: "NotIn" + values: + - kube-system + - kube-node-lease + {{- if .Values.namespaceSelector }} + - key: kubernetes.io/metadata.name + operator: "In" + values: + {{- range .Values.namespaceSelector }} + - {{ . | quote }} + {{- end }} + {{- end }} + rules: + - apiGroups: + - speedscale.com + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - trafficreplays + sideEffects: None + timeoutSeconds: 10 +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-webhook-certs + namespace: {{ .Release.Namespace }} +type: kubernetes.io/tls +data: + ca.crt: {{ $cacrt | b64enc }} + tls.crt: {{ $crt | b64enc }} + tls.key: {{ $key | b64enc }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/configmap.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/configmap.yaml new file mode 100644 index 0000000000..af735e2886 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/configmap.yaml @@ -0,0 +1,41 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: speedscale-operator + namespace: {{ .Release.Namespace }} + annotations: + argocd.argoproj.io/hook: PreSync + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +data: + CLUSTER_NAME: {{ .Values.clusterName }} + IMAGE_PULL_POLICY: {{ .Values.image.pullPolicy }} + IMAGE_PULL_SECRETS: "" + IMAGE_REGISTRY: {{ .Values.image.registry }} + IMAGE_TAG: {{ .Values.image.tag }} + INSTANCE_ID: '{{- $cm := (lookup "v1" "ConfigMap" .Release.Namespace "speedscale-operator") -}}{{ if $cm }}{{ $cm.data.INSTANCE_ID }}{{ else }}{{ ( printf "%s-%s" .Values.clusterName uuidv4 ) }}{{ end }}' + LOG_LEVEL: {{ .Values.logLevel }} + SPEEDSCALE_DLP_CONFIG: {{ .Values.dlp.config }} + SPEEDSCALE_FILTER_RULE: {{ .Values.filterRule }} + TELEMETRY_INTERVAL: 1s + WITH_DLP: {{ .Values.dlp.enabled | quote }} + WITH_INSPECTOR: {{ .Values.dashboardAccess | quote }} + API_KEY_SECRET_NAME: {{ .Values.apiKeySecret | quote }} + DEPLOY_DEMO: {{ .Values.deployDemo | quote }} + GLOBAL_ANNOTATIONS: {{ .Values.globalAnnotations | toJson | quote }} + GLOBAL_LABELS: {{ .Values.globalLabels | toJson | quote }} + {{- if .Values.http_proxy }} + HTTP_PROXY: {{ .Values.http_proxy }} + {{- end }} + {{- if .Values.https_proxy }} + HTTPS_PROXY: {{ .Values.https_proxy }} + {{- end }} + {{- if .Values.no_proxy }} + NO_PROXY: {{ .Values.no_proxy }} + {{- end }} + PRIVILEGED_SIDECARS: {{ .Values.privilegedSidecars | quote }} + DISABLE_SMARTDNS: {{ .Values.disableSidecarSmartReverseDNS | quote }} + SIDECAR_CONFIG: {{ .Values.sidecar | toJson | quote }} + FORWARDER_CONFIG: {{ .Values.forwarder | toJson | quote }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/crds/trafficreplays.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/crds/trafficreplays.yaml new file mode 100644 index 0000000000..5743c5a13c --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/crds/trafficreplays.yaml @@ -0,0 +1,515 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + creationTimestamp: null + name: trafficreplays.speedscale.com +spec: + group: speedscale.com + names: + kind: TrafficReplay + listKind: TrafficReplayList + plural: trafficreplays + shortNames: + - replay + singular: trafficreplay + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.active + name: Active + type: boolean + - jsonPath: .spec.mode + name: Mode + type: string + - jsonPath: .status.conditions[-1:].message + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: TrafficReplay is the Schema for the trafficreplays API + 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: TrafficReplaySpec defines the desired state of TrafficReplay + properties: + buildTag: + description: |- + BuildTag links a unique tag, build hash, etc. to the generated + traffic replay report. That way you can connect the report results to the + version of the code that was tested. + type: string + cleanup: + description: |- + Cleanup is the name of cleanup mode used for this + TrafficReplay. + enum: + - inventory + - all + - none + type: string + collectLogs: + description: |- + CollectLogs enables or disables log collection from target + workload. Defaults to true. + DEPRECATED: use TestReport.ActualConfig.Cluster.CollectLogs + type: boolean + configChecksum: + description: |- + ConfigChecksum, managed my the operator, is the SHA1 checksum of the + configuration. + type: string + customURL: + description: CustomURL allows to specify custom URL to the SUT. + type: string + generatorLowData: + description: |- + GeneratorLowData forces the generator into a high + efficiency/low data output mode. This is ideal for high volume + performance tests. Defaults to false. + DEPRECATED + type: boolean + mode: + description: Mode is the name of replay mode used for this TrafficReplay. + enum: + - full-replay + - responder-only + - generator-only + type: string + needsReport: + description: Indicates whether a responder-only replay needs a report. + type: boolean + proxyMode: + description: |- + ProxyMode defines proxy operational mode used with injected sidecar. + DEPRECATED + type: string + responderLowData: + description: |- + ResponderLowData forces the responder into a high + efficiency/low data output mode. This is ideal for high volume + performance tests. Defaults to false. + DEPRECATED + type: boolean + secretRefs: + description: |- + SecretRefs hold the references to the secrets which contain + various secrets like (e.g. short-lived JWTs to be used by the generator + for authorization with HTTP calls). + items: + description: |- + LocalObjectReference contains enough information to locate the referenced + Kubernetes resource object. + properties: + name: + description: Name of the referent. + type: string + required: + - name + type: object + type: array + sidecar: + description: |- + Sidecar defines sidecar specific configuration. + DEPRECATED: use Workloads + properties: + inject: + description: 'DEPRECATED: do not use' + type: boolean + patch: + description: Patch is .yaml file patch for the Workload + format: byte + type: string + tls: + properties: + in: + description: In provides configuration for sidecar inbound + TLS. + properties: + private: + description: Private is the filename of the TLS inbound + private key. + type: string + public: + description: Public is the filename of the TLS inbound + public key. + type: string + secret: + description: Secret is a secret with the TLS keys to use + for inbound traffic. + type: string + type: object + mutual: + description: Mutual provides configuration for sidecar mutual + TLS. + properties: + private: + description: Private is the filename of the mutual TLS + private key. + type: string + public: + description: Public is the filename of the mutual TLS + public key. + type: string + secret: + description: Secret is a secret with the mutual TLS keys. + type: string + type: object + out: + description: |- + Out enables or disables TLS out on the + sidecar during replay. + type: boolean + type: object + type: object + snapshotID: + description: |- + SnapshotID is the id of the traffic snapshot for this + TrafficReplay. + type: string + testConfigID: + description: |- + TestConfigID is the id of the replay configuration to be used + by the generator and responder for the TrafficReplay. + type: string + timeout: + description: |- + Timeout is the time to wait for replay test to finish. Defaults + to value of the `TIMEOUT` setting of the operator. + type: string + ttlAfterReady: + description: |- + TTLAfterReady provides a TTL (time to live) mechanism to limit + the lifetime of TrafficReplay object that have finished the execution and + reached its final state (either complete or failed). + type: string + workloadRef: + description: |- + WorkloadRef is the reference to the target workload (SUT) for + TrafficReplay. The operations will be performed in the namespace of the + target object. + DEPRECATED: use Workloads + properties: + apiVersion: + description: API version of the referenced object. + type: string + kind: + description: Kind of the referenced object. Defaults to "Deployment". + type: string + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace of the referenced object. Defaults to the + TrafficReplay namespace. + type: string + required: + - name + type: object + workloads: + description: |- + Workloads define target workloads (SUT) for a TrafficReplay. Many + workloads may be provided, or none. Workloads may be modified and + restarted during replay to configure communication with a responder. + items: + description: |- + Workload represents a Kubernetes workload to be targeted during replay and + associated settings. + properties: + customURI: + description: CustomURI will be target of the traffic instead + of directly targeting workload + type: string + inTrafficKey: + description: 'DEPRECATED: use InTrafficKeys' + type: string + inTrafficKeys: + description: 'DEPRECATED: use Tests' + items: + type: string + type: array + mocks: + description: |- + Mocks are strings used to identify slices of outbound snapshot traffic to + mock for this workload and maps directly to a snapshot's `OutTraffic` + field. Snapshot egress traffic can be split across multiple slices where + each slice contains part of the traffic. A workload may specify multiple + keys and multiple workloads may specify the same key. + + + Only the traffic slices defined here will be mocked. A workload with no + keys defined will not mock any traffic. Pass '*' to mock all traffic. + + + Mock strings may only match part of the snapshot's `OutTraffic` key if the + string matches exactly one key. For example, the test string + `foo.example.com` would match the `OutTraffic` key of + my-service:foo.example.com:8080, as long as no other keys would match + `foo.example.com`. Multiple mocks must be specified for multiple keys + unless using '*'. + items: + type: string + type: array + outTrafficKeys: + description: 'DEPRECATED: use Mocks' + items: + type: string + type: array + ref: + description: |- + Ref is a reference to a cluster workload, like a deployment or a + statefulset. + properties: + apiVersion: + description: API version of the referenced object. + type: string + kind: + description: Kind of the referenced object. Defaults to + "Deployment". + type: string + name: + description: Name of the referenced object. + type: string + namespace: + description: Namespace of the referenced object. Defaults + to the TrafficReplay namespace. + type: string + required: + - name + type: object + routing: + description: Routing configures how workloads route egress traffic + to responders + enum: + - hostalias + - nat + type: string + sidecar: + description: |- + TODO: this is not implemented, come back and replace deprecated Sidecar with workload specific settings + Sidecar defines sidecar specific configuration. + properties: + inject: + description: 'DEPRECATED: do not use' + type: boolean + patch: + description: Patch is .yaml file patch for the Workload + format: byte + type: string + tls: + properties: + in: + description: In provides configuration for sidecar inbound + TLS. + properties: + private: + description: Private is the filename of the TLS + inbound private key. + type: string + public: + description: Public is the filename of the TLS inbound + public key. + type: string + secret: + description: Secret is a secret with the TLS keys + to use for inbound traffic. + type: string + type: object + mutual: + description: Mutual provides configuration for sidecar + mutual TLS. + properties: + private: + description: Private is the filename of the mutual + TLS private key. + type: string + public: + description: Public is the filename of the mutual + TLS public key. + type: string + secret: + description: Secret is a secret with the mutual + TLS keys. + type: string + type: object + out: + description: |- + Out enables or disables TLS out on the + sidecar during replay. + type: boolean + type: object + type: object + tests: + description: |- + Tests are strings used to identify slices of inbound snapshot traffic this + workload is targeting and maps directly to a snapshot's `InTraffic` field. + Snapshot ingress traffic can be split across multiple slices where each + slice contains part of the traffic. A key must only be specified once + across all workloads, but a workload may specify multiple keys. Pass '*' + to match all keys. + + + Test strings may only match part of the snapshot's `InTraffic` key if the + string matches exactly one key. For example, the test string + `foo.example.com` would match the `InTraffic` key of + my-service:foo.example.com:8080, as long as no other keys would match + `foo.example.com` + + + This field is optional in the spec to provide support for single-workload + and legacy replays, but must be specified for multi-workload replays in + order to provide deterministic replay configuration. + items: + type: string + type: array + type: object + type: array + required: + - snapshotID + - testConfigID + type: object + status: + default: + observedGeneration: -1 + description: TrafficReplayStatus defines the observed state of TrafficReplay + properties: + active: + description: Active indicates whether this traffic replay is currently + underway or not. + type: boolean + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + finishedTime: + description: Information when the traffic replay has finished. + format: date-time + type: string + initializedTime: + description: Information when the test environment was successfully + prepared. + format: date-time + type: string + lastHeartbeatTime: + description: 'DEPRECATED: will not be set' + format: date-time + type: string + observedGeneration: + description: ObservedGeneration is the last observed generation. + format: int64 + type: integer + reconcileFailures: + description: |- + ReconcileFailures is the number of times the traffic replay controller + experienced an error during the reconciliation process. The traffic + replay will be deleted if too many errors occur. + format: int64 + type: integer + reportID: + description: The id of the traffic replay report created. + type: string + reportURL: + description: The url to the traffic replay report. + type: string + startedTime: + description: Information when the traffic replay has started. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/deployments.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/deployments.yaml new file mode 100644 index 0000000000..e5f3292579 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/deployments.yaml @@ -0,0 +1,132 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + operator.speedscale.com/ignore: "true" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + labels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + {{- if .Values.globalLabels }} +{{ toYaml .Values.globalLabels | indent 4}} + {{- end }} + name: speedscale-operator + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + strategy: + type: Recreate + template: + metadata: + annotations: + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 8}} + {{- end }} + labels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + {{- if .Values.globalLabels }} +{{ toYaml .Values.globalLabels | indent 8}} + {{- end }} + spec: + containers: + - command: + - /operator + env: + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + envFrom: + - configMapRef: + name: speedscale-operator + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#container-v1-core + # When a key exists in multiple sources, the value associated with the last source will take precedence. + # Values defined by an Env with a duplicate key will take precedence. + - configMapRef: + name: speedscale-operator-override + optional: true + - secretRef: + name: '{{ ne .Values.apiKeySecret "" | ternary .Values.apiKeySecret "speedscale-apikey" }}' + optional: false + image: '{{ .Values.image.registry }}/operator:{{ .Values.image.tag }}' + imagePullPolicy: {{ .Values.image.pullPolicy }} + livenessProbe: + failureThreshold: 5 + httpGet: + path: /healthz + port: health-check + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 + name: operator + ports: + - containerPort: 443 + name: webhook-server + - containerPort: 8081 + name: health-check + readinessProbe: + failureThreshold: 10 + httpGet: + path: /readyz + port: health-check + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 5 + resources: {{- toYaml .Values.operator.resources | nindent 10 }} + securityContext: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: false + # Run as root to bind 443 https://github.com/kubernetes/kubernetes/issues/56374 + runAsUser: 0 + volumeMounts: + - mountPath: /tmp + name: tmp + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: webhook-certs + readOnly: true + - mountPath: /etc/ssl/speedscale + name: speedscale-tls-out + readOnly: true + hostNetwork: {{ .Values.hostNetwork }} + securityContext: + runAsNonRoot: true + serviceAccountName: speedscale-operator + terminationGracePeriodSeconds: 10 + volumes: + - emptyDir: {} + name: tmp + - name: webhook-certs + secret: + secretName: speedscale-webhook-certs + - name: speedscale-tls-out + secret: + secretName: speedscale-certs + {{- if .Values.affinity }} + affinity: {{ toYaml .Values.affinity | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{ toYaml .Values.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }} + {{- end }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/hooks.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/hooks.yaml new file mode 100644 index 0000000000..3e8231f194 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/hooks.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "4" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-operator-pre-install + namespace: {{ .Release.Namespace }} + labels: + {{- if .Values.globalLabels }} +{{ toYaml .Values.globalLabels | indent 4}} + {{- end }} +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 30 + template: + metadata: + annotations: + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 8}} + {{- end }} + creationTimestamp: null + labels: + {{- if .Values.globalLabels }} +{{ toYaml .Values.globalLabels | indent 8}} + {{- end }} + spec: + containers: + - args: + - |- + # ensure valid settings before the chart reports a successfull install + {{- if .Values.http_proxy }} + HTTP_PROXY={{ .Values.http_proxy | quote }} \ + {{- end }} + {{- if .Values.https_proxy }} + HTTPS_PROXY={{ .Values.https_proxy | quote }} \ + {{- end }} + {{- if .Values.no_proxy }} + NO_PROXY={{ .Values.no_proxy | quote }} \ + {{- end }} + speedctl init --overwrite --no-rcfile-update \ + --api-key $SPEEDSCALE_API_KEY \ + --app-url $SPEEDSCALE_APP_URL + + # in case we're in istio + curl -X POST http://127.0.0.1:15000/quitquitquit || true + command: + - sh + - -c + envFrom: + - secretRef: + name: '{{ ne .Values.apiKeySecret "" | ternary .Values.apiKeySecret "speedscale-apikey" }}' + optional: false + image: '{{ .Values.image.registry }}/speedscale-cli:{{ .Values.image.tag }}' + imagePullPolicy: {{ .Values.image.pullPolicy }} + name: speedscale-cli + resources: {} + restartPolicy: Never + {{- if .Values.affinity }} + affinity: {{ toYaml .Values.affinity | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{ toYaml .Values.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }} + {{- end }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/rbac.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/rbac.yaml new file mode 100644 index 0000000000..e1ea42d999 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/rbac.yaml @@ -0,0 +1,244 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: speedscale-operator + {{- if .Values.globalAnnotations }} + annotations: {{ toYaml .Values.globalAnnotations | nindent 4 }} + {{- end }} +rules: +- apiGroups: + - apps + resources: + - deployments + - statefulsets + - daemonsets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - replicasets + verbs: + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - get + - list +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + verbs: + - get + - list +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - configmaps + - secrets + - pods + - services + - serviceaccounts + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list +- apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - metrics.k8s.io + resources: + - pods + verbs: + - get + - list + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.istio.io + resources: + - envoyfilters + - sidecars + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - security.istio.io + resources: + - peerauthentications + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - speedscale.com + resources: + - trafficreplays + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - speedscale.com + resources: + - trafficreplays/status + verbs: + - get + - update + - patch +- apiGroups: + - argoproj.io + resources: + - rollouts + verbs: + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: speedscale-operator + {{- if .Values.globalAnnotations }} + annotations: {{ toYaml .Values.globalAnnotations | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: speedscale-operator +subjects: +- kind: ServiceAccount + name: speedscale-operator + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + creationTimestamp: null + labels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + name: speedscale-operator + namespace: {{ .Release.Namespace }} + {{- if .Values.globalAnnotations }} + annotations: {{ toYaml .Values.globalAnnotations | nindent 4 }} + {{- end }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/secrets.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/secrets.yaml new file mode 100644 index 0000000000..1fb6999e4c --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/secrets.yaml @@ -0,0 +1,18 @@ +--- +{{ if .Values.apiKey }} +apiVersion: v1 +kind: Secret +metadata: + name: speedscale-apikey + namespace: {{ .Release.Namespace }} + annotations: + helm.sh/hook: pre-install + helm.sh/hook-weight: "3" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} +type: Opaque +data: + SPEEDSCALE_API_KEY: {{ .Values.apiKey | b64enc }} + SPEEDSCALE_APP_URL: {{ .Values.appUrl | b64enc }} +{{ end }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/services.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/services.yaml new file mode 100644 index 0000000000..f9da2c25c1 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/services.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + name: speedscale-operator + namespace: {{ .Release.Namespace }} + {{- if .Values.globalAnnotations }} + annotations: {{ toYaml .Values.globalAnnotations | nindent 4 }} + {{- end }} +spec: + ports: + - port: 443 + protocol: TCP + selector: + app: speedscale-operator + controlplane.speedscale.com/component: operator +status: + loadBalancer: {} diff --git a/charts/speedscale/speedscale-operator/2.2.314/templates/tls.yaml b/charts/speedscale/speedscale-operator/2.2.314/templates/tls.yaml new file mode 100644 index 0000000000..4a24562884 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/templates/tls.yaml @@ -0,0 +1,183 @@ +{{- $crt := "" -}} +{{- $key := "" -}} +{{- $s := (lookup "v1" "Secret" .Release.Namespace "speedscale-certs") -}} +{{- if $s -}} +{{- $crt = index $s.data "tls.crt" | b64dec -}} +{{- $key = index $s.data "tls.key" | b64dec -}} +{{ else }} +{{- $cert := genCA "Speedscale" 3650 -}} +{{- $crt = $cert.Cert -}} +{{- $key = $cert.Key -}} +{{- end -}} +--- +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "5" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-operator-create-jks + namespace: {{ .Release.Namespace }} + labels: + {{- if .Values.globalLabels }} +{{ toYaml .Values.globalLabels | indent 4}} + {{- end }} +spec: + backoffLimit: 0 + ttlSecondsAfterFinished: 30 + template: + metadata: + annotations: + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 8}} + {{- end }} + creationTimestamp: null + labels: + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 8}} + {{- end }} + spec: + containers: + - args: + - |- + keytool -keystore /usr/lib/jvm/jre/lib/security/cacerts -importcert -noprompt -trustcacerts -storepass changeit -alias speedscale -file /etc/ssl/speedscale/tls.crt + kubectl -n ${POD_NAMESPACE} delete secret speedscale-jks || true + kubectl -n ${POD_NAMESPACE} create secret generic speedscale-jks --from-file=cacerts.jks=/usr/lib/jvm/jre/lib/security/cacerts + + # in case we're in istio + curl -X POST http://127.0.0.1:15000/quitquitquit || true + command: + - sh + - -c + volumeMounts: + - mountPath: /etc/ssl/speedscale + name: speedscale-tls-out + readOnly: true + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + envFrom: + - secretRef: + name: '{{ ne .Values.apiKeySecret "" | ternary .Values.apiKeySecret "speedscale-apikey" }}' + optional: false + image: '{{ .Values.image.registry }}/amazoncorretto' + imagePullPolicy: {{ .Values.image.pullPolicy }} + name: create-jks + resources: {} + restartPolicy: Never + serviceAccountName: speedscale-operator-provisioning + volumes: + - name: speedscale-tls-out + secret: + secretName: speedscale-certs + {{- if .Values.affinity }} + affinity: {{ toYaml .Values.affinity | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{ toYaml .Values.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{ toYaml .Values.nodeSelector | nindent 8 }} + {{- end }} +--- +apiVersion: v1 +automountServiceAccountToken: true +kind: ServiceAccount +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "1" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + labels: + app: speedscale-operator + controlplane.speedscale.com/component: operator + name: speedscale-operator-provisioning + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "2" + creationTimestamp: null + name: speedscale-operator-provisioning +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "3" + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-operator-provisioning +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: speedscale-operator-provisioning +subjects: +- kind: ServiceAccount + name: speedscale-operator-provisioning + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: before-hook-creation + {{- if .Values.globalAnnotations }} +{{ toYaml .Values.globalAnnotations | indent 4}} + {{- end }} + creationTimestamp: null + name: speedscale-certs + namespace: {{ .Release.Namespace }} +type: kubernetes.io/tls +data: + tls.crt: {{ $crt | b64enc }} + tls.key: {{ $key | b64enc }} diff --git a/charts/speedscale/speedscale-operator/2.2.314/values.yaml b/charts/speedscale/speedscale-operator/2.2.314/values.yaml new file mode 100644 index 0000000000..3b80ef6b15 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.314/values.yaml @@ -0,0 +1,133 @@ +# An API key is required to connect to the Speedscale cloud. +# If you need a key email support@speedscale.com. +apiKey: "" + +# A secret name can be referenced instead of the api key itself. +# The secret must be of the format: +# +# type: Opaque +# data: +# SPEEDSCALE_API_KEY: +# SPEEDSCALE_APP_URL: +apiKeySecret: "" + +# Speedscale domain to use. +appUrl: "app.speedscale.com" + +# The name of your cluster. +clusterName: "my-cluster" + +# Speedscale components image settings. +image: + registry: gcr.io/speedscale + tag: v2.2.314 + pullPolicy: Always + +# Log level for Speedscale components. +logLevel: "info" + +# Namespaces to be watched by Speedscale Operator as a list of names. +namespaceSelector: [] + +# Instructs operator to deploy resources necessary to interact with your cluster from the Speedscale dashboard. +dashboardAccess: true + +# Filter Rule to apply to the Speedscale Forwarder +filterRule: "standard" + +# Data Loss Prevention settings. +dlp: + # Instructs operator to enable data loss prevention features + enabled: false + + # Configuration for data loss prevention + config: "standard" + +# If the operator pod/webhooks need to be on the host network. +# This is only needed if the control plane cannot connect directly to a pod +# for eg. if Calico is used as EKS's default networking +# https://docs.tigera.io/calico/3.25/getting-started/kubernetes/managed-public-cloud/eks#install-eks-with-calico-networking +hostNetwork: false + +# A set of annotations to be applied to all Speedscale related deployments, +# services, jobs, pods, etc. +# +# Example: +# annotation.first: value +# annotation.second: value +globalAnnotations: {} + +# A set of labels to be applied to all Speedscale related deployments, +# services, jobs, pods, etc. +# +# Example: +# label1: value +# label2: value +globalLabels: {} + +# A full affinity object as detailed: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity +affinity: {} + +# The list of tolerations as detailed: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ +tolerations: [] + +# A nodeselector object as detailed: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes/ +nodeSelector: {} + +# Deploy a demo app at startup. Set this to an empty string to not deploy. +# Valid values: ["java", ""] +deployDemo: "java" + +# Proxy connection settings if required by your network. These translate to standard proxy environment +# variables HTTP_PROXY, HTTPS_PROXY, and NO_PROXY +http_proxy: "" +https_proxy: "" +no_proxy: "" + +# control if sidecar init containers should run with privileged set +privilegedSidecars: false + +# control if the sidecar should enable/disable use of the smart dns lookup feature (requires NET_ADMIN) +disableSidecarSmartReverseDNS: false + +# Operator settings. These limits are recommended unless you have a cluster +# with a very large number of workloads (for eg. 10k+ deployments, replicasets, etc.). +operator: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +# Default sidecar settings. Example: +# sidecar: +# resources: +# limits: +# cpu: 500m +# memory: 512Mi +# ephemeral-storage: 100Mi +# requests: +# cpu: 10m +# memory: 32Mi +# ephemeral-storage: 100Mi +# ignore_src_hosts: example.com, example.org +# ignore_src_ips: 8.8.8.8, 1.1.1.1 +# ignore_dst_hosts: example.com, example.org +# ignore_dst_ips: 8.8.8.8, 1.1.1.1 +# insert_init_first: false +# tls_out: false +# reinitialize_iptables: false +sidecar: {} + +# Forwarder settings +# forwarder: +# resources: +# limits: +# cpu: 500m +# memory: 500M +# requests: +# cpu: 300m +# memory: 250M +forwarder: {} diff --git a/index.yaml b/index.yaml index 4f610fc332..5c1901ae2d 100644 --- a/index.yaml +++ b/index.yaml @@ -241,6 +241,40 @@ entries: - assets/amd/amd-gpu-0.9.0.tgz version: 0.9.0 artifactory-ha: + - annotations: + artifactoryServiceVersion: 7.90.12 + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: JFrog Artifactory HA + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: artifactory-ha + apiVersion: v2 + appVersion: 7.90.9 + created: "2024-08-29T00:50:31.767698312Z" + dependencies: + - condition: postgresql.enabled + name: postgresql + repository: file://./charts/postgresql + version: 10.3.18 + description: Universal Repository Manager supporting all major packaging formats, + build tools and CI servers. + digest: ae43649a26ce45e23b9b57d71832276437a0f966e1232c3462a93515e91dc3a3 + home: https://www.jfrog.com/artifactory/ + icon: file://assets/icons/artifactory-ha.png + keywords: + - artifactory + - jfrog + - devops + kubeVersion: '>= 1.19.0-0' + maintainers: + - email: installers@jfrog.com + name: Chart Maintainers at JFrog + name: artifactory-ha + sources: + - https://github.com/jfrog/charts + type: application + urls: + - assets/jfrog/artifactory-ha-107.90.9.tgz + version: 107.90.9 - annotations: artifactoryServiceVersion: 7.90.11 catalog.cattle.io/certified: partner @@ -1540,6 +1574,40 @@ entries: - assets/jfrog/artifactory-ha-107.55.14.tgz version: 107.55.14 artifactory-jcr: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: JFrog Container Registry + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: artifactory-jcr + apiVersion: v2 + appVersion: 7.90.9 + created: "2024-08-29T00:50:32.117871887Z" + dependencies: + - name: artifactory + repository: file://./charts/artifactory + version: 107.90.9 + description: JFrog Container Registry + digest: 5b72dbae1bcd53af7b425d8980677225d8c1db8ea39a530cef7130c0cc90b104 + home: https://jfrog.com/container-registry/ + icon: file://assets/icons/artifactory-jcr.png + keywords: + - artifactory + - jfrog + - container + - registry + - devops + - jfrog-container-registry + kubeVersion: '>= 1.19.0-0' + maintainers: + - email: helm@jfrog.com + name: Chart Maintainers at JFrog + name: artifactory-jcr + sources: + - https://github.com/jfrog/charts + type: application + urls: + - assets/jfrog/artifactory-jcr-107.90.9.tgz + version: 107.90.9 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: JFrog Container Registry @@ -6776,10 +6844,27 @@ entries: catalog.cattle.io/featured: "1" catalog.cattle.io/release-name: cost-analyzer apiVersion: v2 + appVersion: 2.3.5 + created: "2024-08-29T00:50:32.926270101Z" + description: Kubecost Helm chart - monitor your cloud costs! + digest: 82661dfa56a8c1ba09e4e3cdc7eec9d1d3f40e0f3be780462bb4dd0eb9abff9d + icon: file://assets/icons/cost-analyzer.png + name: cost-analyzer + urls: + - assets/kubecost/cost-analyzer-2.3.5.tgz + version: 2.3.5 + - annotations: + artifacthub.io/links: | + - name: Homepage + url: https://www.kubecost.com + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Kubecost + catalog.cattle.io/release-name: cost-analyzer + apiVersion: v2 appVersion: 2.3.4 created: "2024-07-31T00:38:59.817727957Z" description: Kubecost Helm chart - monitor your cloud costs! - digest: 4d2f056db72c23698e64e8858e8865a8c418b85166f710ef27757f4a14c450d0 + digest: 80de05d38b9172889be7ccc41708a5062f1eee9ba21a8ea1a3346c7ca55d3ed4 icon: file://assets/icons/cost-analyzer.png name: cost-analyzer urls: @@ -9820,6 +9905,35 @@ entries: - assets/dynatrace/dynatrace-operator-0.12.0.tgz version: 0.12.0 external-secrets: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: External Secrets Operator + catalog.cattle.io/kube-version: '>= 1.19.0-0' + catalog.cattle.io/release-name: external-secrets + apiVersion: v2 + appVersion: v0.10.2 + created: "2024-08-29T00:50:30.719333937Z" + dependencies: + - condition: bitwarden-sdk-server.enabled + name: bitwarden-sdk-server + repository: file://./charts/bitwarden-sdk-server + version: v0.1.4 + description: External secret management for Kubernetes + digest: 930698bc7bece02c5e473530cf60a21876fa05946fc1ad1dbec21dd629f31713 + home: https://github.com/external-secrets/external-secrets + icon: file://assets/icons/external-secrets.png + keywords: + - kubernetes-external-secrets + - secrets + kubeVersion: '>= 1.19.0-0' + maintainers: + - email: kellinmcavoy@gmail.com + name: mcavoyk + name: external-secrets + type: application + urls: + - assets/external-secrets/external-secrets-0.10.2.tgz + version: 0.10.2 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: External Secrets Operator @@ -15145,6 +15259,64 @@ entries: - assets/intel/intel-device-plugins-sgx-0.26.1.tgz version: 0.26.1 jenkins: + - annotations: + artifacthub.io/category: integration-delivery + artifacthub.io/changes: | + - Update `configuration-as-code` to version `1850.va_a_8c31d3158b_` + artifacthub.io/images: | + - name: jenkins + image: docker.io/jenkins/jenkins:2.462.1-jdk17 + - name: k8s-sidecar + image: docker.io/kiwigrid/k8s-sidecar:1.27.5 + - name: inbound-agent + image: jenkins/inbound-agent:3261.v9c670a_4748a_9-1 + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: Chart Source + url: https://github.com/jenkinsci/helm-charts/tree/main/charts/jenkins + - name: Jenkins + url: https://www.jenkins.io/ + - name: support + url: https://github.com/jenkinsci/helm-charts/issues + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Jenkins + catalog.cattle.io/kube-version: '>=1.14-0' + catalog.cattle.io/release-name: jenkins + apiVersion: v2 + appVersion: 2.462.1 + created: "2024-08-29T00:50:31.439302137Z" + description: 'Jenkins - Build great things at any scale! As the leading open source + automation server, Jenkins provides over 1800 plugins to support building, deploying + and automating any project. ' + digest: b1debd4c70e98afa9cb64b0764ab2d8da35356214a5ca736edabdc55b543e4cf + home: https://www.jenkins.io/ + icon: file://assets/icons/jenkins.svg + keywords: + - jenkins + - ci + - devops + kubeVersion: '>=1.14-0' + maintainers: + - email: maor.friedman@redhat.com + name: maorfr + - email: mail@torstenwalter.de + name: torstenwalter + - email: garridomota@gmail.com + name: mogaal + - email: wmcdona89@gmail.com + name: wmcdona89 + - email: timjacomb1@gmail.com + name: timja + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-inbound-agent + - https://github.com/maorfr/kube-tasks + - https://github.com/jenkinsci/configuration-as-code-plugin + type: application + urls: + - assets/jenkins/jenkins-5.5.12.tgz + version: 5.5.12 - annotations: artifacthub.io/category: integration-delivery artifacthub.io/changes: | @@ -25173,6 +25345,32 @@ entries: - assets/minio/minio-operator-5.0.6.tgz version: 5.0.6 nats: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: NATS Server + catalog.cattle.io/kube-version: '>=1.16-0' + catalog.cattle.io/release-name: nats + apiVersion: v2 + appVersion: 2.10.19 + created: "2024-08-29T00:50:33.255919839Z" + description: A Helm chart for the NATS.io High Speed Cloud Native Distributed + Communications Technology. + digest: 15f8a52f4ed5de3aa81cf5538b7a211032c2c9185ee88ea031477b8366733226 + home: http://github.com/nats-io/k8s + icon: file://assets/icons/nats.png + keywords: + - nats + - messaging + - cncf + kubeVersion: '>=1.16-0' + maintainers: + - email: info@nats.io + name: The NATS Authors + url: https://github.com/nats-io + name: nats + urls: + - assets/nats/nats-1.2.3.tgz + version: 1.2.3 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: NATS Server @@ -34423,6 +34621,37 @@ entries: - assets/btp/sextant-2.2.21.tgz version: 2.2.21 speedscale-operator: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: Speedscale Operator + catalog.cattle.io/kube-version: '>= 1.17.0-0' + catalog.cattle.io/release-name: speedscale-operator + apiVersion: v1 + appVersion: 2.2.314 + created: "2024-08-29T00:50:34.440816717Z" + description: Stress test your APIs with real world scenarios. Collect and replay + traffic without scripting. + digest: cadba81773e77fed0073de98cadc8bcc1fce9998060ffa9e1a2a54cd9afba61d + home: https://speedscale.com + icon: file://assets/icons/speedscale-operator.png + keywords: + - speedscale + - test + - testing + - regression + - reliability + - load + - replay + - network + - traffic + kubeVersion: '>= 1.17.0-0' + maintainers: + - email: support@speedscale.com + name: Speedscale Support + name: speedscale-operator + urls: + - assets/speedscale/speedscale-operator-2.2.314.tgz + version: 2.2.314 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Speedscale Operator @@ -40669,4 +40898,4 @@ entries: urls: - assets/netfoundry/ziti-host-1.5.1.tgz version: 1.5.1 -generated: "2024-08-28T00:50:01.320057872Z" +generated: "2024-08-29T00:50:30.107476326Z"