From fde4bea09519b4fd2bd1f952ed4b298a51af0554 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Oct 2024 00:39:13 +0000 Subject: [PATCH] Added chart versions: jfrog/artifactory-ha: - 107.98.7 jfrog/artifactory-jcr: - 107.98.7 new-relic/nri-bundle: - 5.0.98 speedscale/speedscale-operator: - 2.2.606 --- assets/jfrog/artifactory-ha-107.98.7.tgz | Bin 0 -> 219954 bytes assets/jfrog/artifactory-jcr-107.98.7.tgz | Bin 0 -> 461951 bytes assets/new-relic/nri-bundle-5.0.98.tgz | Bin 0 -> 366915 bytes .../speedscale-operator-2.2.606.tgz | Bin 0 -> 17062 bytes .../jfrog/artifactory-ha/107.98.7/.helmignore | 24 + .../artifactory-ha/107.98.7/CHANGELOG.md | 1493 +++ .../jfrog/artifactory-ha/107.98.7/Chart.lock | 6 + .../jfrog/artifactory-ha/107.98.7/Chart.yaml | 32 + charts/jfrog/artifactory-ha/107.98.7/LICENSE | 201 + .../jfrog/artifactory-ha/107.98.7/README.md | 69 + .../artifactory-ha/107.98.7/app-readme.md | 16 + .../107.98.7/charts/postgresql/.helmignore | 21 + .../107.98.7/charts/postgresql/Chart.lock | 6 + .../107.98.7/charts/postgresql/Chart.yaml | 29 + .../107.98.7/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.98.7/charts/postgresql/values.yaml | 824 ++ .../107.98.7/ci/access-tls-values.yaml | 34 + .../107.98.7/ci/default-values.yaml | 32 + .../107.98.7/ci/global-values.yaml | 255 + .../107.98.7/ci/large-values.yaml | 85 + .../107.98.7/ci/loggers-values.yaml | 43 + .../107.98.7/ci/medium-values.yaml | 85 + .../ci/migration-disabled-values.yaml | 31 + .../107.98.7/ci/nginx-autoreload-values.yaml | 53 + .../ci/rtsplit-access-tls-values.yaml | 106 + .../107.98.7/ci/rtsplit-values.yaml | 155 + .../107.98.7/ci/small-values.yaml | 87 + .../107.98.7/ci/test-values.yaml | 85 + .../107.98.7/files/binarystore.xml | 444 + .../107.98.7/files/installer-info.json | 32 + .../artifactory-ha/107.98.7/files/migrate.sh | 4311 ++++++++ .../107.98.7/files/migrationHelmInfo.yaml | 27 + .../107.98.7/files/migrationStatus.sh | 44 + .../files/nginx-artifactory-conf.yaml | 108 + .../107.98.7/files/nginx-main-conf.yaml | 83 + .../artifactory-ha/107.98.7/files/system.yaml | 174 + .../107.98.7/logo/artifactory-logo.png | Bin 0 -> 82419 bytes .../artifactory-ha/107.98.7/questions.yml | 424 + .../107.98.7/sizing/artifactory-2xlarge.yaml | 160 + .../107.98.7/sizing/artifactory-large.yaml | 160 + .../107.98.7/sizing/artifactory-medium.yaml | 160 + .../107.98.7/sizing/artifactory-small.yaml | 159 + .../107.98.7/sizing/artifactory-xlarge.yaml | 159 + .../107.98.7/sizing/artifactory-xsmall.yaml | 161 + .../107.98.7/templates/NOTES.txt | 149 + .../107.98.7/templates/_helpers.tpl | 589 ++ .../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 | 1596 +++ .../templates/artifactory-primary-pdb.yaml | 24 + .../artifactory-primary-service.yaml | 61 + .../artifactory-primary-statefulset.yaml | 1764 ++++ .../templates/artifactory-priority-class.yaml | 9 + .../107.98.7/templates/artifactory-role.yaml | 14 + .../templates/artifactory-rolebinding.yaml | 19 + .../templates/artifactory-secrets.yaml | 30 + .../templates/artifactory-service-grpc.yaml | 49 + .../templates/artifactory-service.yaml | 74 + .../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.98.7/templates/ingress-grpc.yaml | 80 + .../107.98.7/templates/ingress.yaml | 106 + .../107.98.7/templates/logger-configmap.yaml | 63 + .../templates/nginx-artifactory-conf.yaml | 18 + .../templates/nginx-certificate-secret.yaml | 14 + .../107.98.7/templates/nginx-conf.yaml | 18 + .../107.98.7/templates/nginx-deployment.yaml | 221 + .../107.98.7/templates/nginx-pdb.yaml | 23 + .../107.98.7/templates/nginx-pvc.yaml | 26 + .../templates/nginx-scripts-conf.yaml | 52 + .../107.98.7/templates/nginx-service.yaml | 94 + .../jfrog/artifactory-ha/107.98.7/values.yaml | 1905 ++++ .../artifactory-jcr/107.98.7/CHANGELOG.md | 206 + .../jfrog/artifactory-jcr/107.98.7/Chart.yaml | 30 + charts/jfrog/artifactory-jcr/107.98.7/LICENSE | 201 + .../jfrog/artifactory-jcr/107.98.7/README.md | 125 + .../artifactory-jcr/107.98.7/app-readme.md | 18 + .../107.98.7/charts/artifactory/.helmignore | 24 + .../107.98.7/charts/artifactory/CHANGELOG.md | 1388 +++ .../107.98.7/charts/artifactory/Chart.lock | 6 + .../107.98.7/charts/artifactory/Chart.yaml | 24 + .../107.98.7/charts/artifactory/LICENSE | 201 + .../107.98.7/charts/artifactory/README.md | 60 + .../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 | 431 + .../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 | 108 + .../artifactory/files/nginx-main-conf.yaml | 83 + .../charts/artifactory/files/system.yaml | 167 + .../artifactory/logo/artifactory-logo.png | Bin 0 -> 82419 bytes .../sizing/artifactory-2xlarge.yaml | 158 + .../artifactory/sizing/artifactory-large.yaml | 158 + .../sizing/artifactory-medium.yaml | 158 + .../artifactory/sizing/artifactory-small.yaml | 158 + .../sizing/artifactory-xlarge.yaml | 158 + .../sizing/artifactory-xsmall.yaml | 160 + .../charts/artifactory/templates/NOTES.txt | 106 + .../charts/artifactory/templates/_helpers.tpl | 544 + .../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-grpc.yaml | 44 + .../templates/artifactory-service.yaml | 65 + .../templates/artifactory-serviceaccount.yaml | 17 + .../templates/artifactory-statefulset.yaml | 1668 +++ .../templates/artifactory-system-yaml.yaml | 16 + .../templates/artifactory-unified-secret.yaml | 94 + .../templates/filebeat-configmap.yaml | 15 + .../artifactory/templates/ingress-grpc.yaml | 80 + .../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.98.7/charts/artifactory/values.yaml | 1817 ++++ .../107.98.7/ci/default-values.yaml | 7 + .../107.98.7/logo/jcr-logo.png | Bin 0 -> 77047 bytes .../artifactory-jcr/107.98.7/questions.yml | 271 + .../107.98.7/templates/NOTES.txt | 1 + .../artifactory-jcr/107.98.7/values.yaml | 75 + .../new-relic/nri-bundle/5.0.98/.helmignore | 22 + charts/new-relic/nri-bundle/5.0.98/Chart.lock | 39 + charts/new-relic/nri-bundle/5.0.98/Chart.yaml | 85 + charts/new-relic/nri-bundle/5.0.98/README.md | 200 + .../nri-bundle/5.0.98/README.md.gotmpl | 166 + .../new-relic/nri-bundle/5.0.98/app-readme.md | 5 + .../charts/k8s-agents-operator/.helmignore | 23 + .../charts/k8s-agents-operator/Chart.lock | 6 + .../charts/k8s-agents-operator/Chart.yaml | 20 + .../charts/k8s-agents-operator/README.md | 278 + .../k8s-agents-operator/README.md.gotmpl | 230 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../k8s-agents-operator/templates/NOTES.txt | 36 + .../templates/_helpers.tpl | 7 + .../k8s-agents-operator/templates/_naming.tpl | 52 + .../k8s-agents-operator/templates/_tls.tpl | 40 + .../templates/certmanager.yaml | 30 + .../templates/deployment.yaml | 95 + .../templates/instrumentation-crd.yaml | 407 + .../templates/leader-election-rbac.yaml | 51 + .../templates/manager-rbac.yaml | 88 + .../templates/newrelic_license_secret.yaml | 0 .../templates/proxy-rbac.yaml | 35 + .../templates/reader-rbac.yaml | 11 + .../k8s-agents-operator/templates/secret.yaml | 19 + .../templates/service.yaml | 15 + .../templates/webhook-configuration.yaml | 134 + .../templates/webhook-service.yaml | 14 + .../tests/cert_manager_test.yaml | 85 + .../tests/webhook_ssl_test.yaml | 176 + .../charts/k8s-agents-operator/values.yaml | 93 + .../charts/kube-state-metrics/.helmignore | 21 + .../charts/kube-state-metrics/Chart.yaml | 26 + .../charts/kube-state-metrics/README.md | 85 + .../kube-state-metrics/templates/NOTES.txt | 23 + .../kube-state-metrics/templates/_helpers.tpl | 156 + .../templates/ciliumnetworkpolicy.yaml | 33 + .../templates/clusterrolebinding.yaml | 20 + .../templates/crs-configmap.yaml | 16 + .../templates/deployment.yaml | 336 + .../templates/extra-manifests.yaml | 4 + .../templates/kubeconfig-secret.yaml | 12 + .../templates/networkpolicy.yaml | 43 + .../kube-state-metrics/templates/pdb.yaml | 18 + .../templates/podsecuritypolicy.yaml | 39 + .../templates/psp-clusterrole.yaml | 19 + .../templates/psp-clusterrolebinding.yaml | 16 + .../templates/rbac-configmap.yaml | 22 + .../kube-state-metrics/templates/role.yaml | 212 + .../templates/rolebinding.yaml | 24 + .../kube-state-metrics/templates/service.yaml | 53 + .../templates/serviceaccount.yaml | 18 + .../templates/servicemonitor.yaml | 120 + .../templates/stsdiscovery-role.yaml | 26 + .../templates/stsdiscovery-rolebinding.yaml | 17 + .../templates/verticalpodautoscaler.yaml | 44 + .../charts/kube-state-metrics/values.yaml | 542 + .../newrelic-infra-operator/.helmignore | 1 + .../charts/newrelic-infra-operator/Chart.lock | 6 + .../charts/newrelic-infra-operator/Chart.yaml | 35 + .../charts/newrelic-infra-operator/README.md | 114 + .../newrelic-infra-operator/README.md.gotmpl | 77 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-values.yaml | 39 + .../templates/NOTES.txt | 4 + .../templates/_helpers.tpl | 136 + .../job-patch/clusterrole.yaml | 27 + .../job-patch/clusterrolebinding.yaml | 20 + .../job-patch/job-createSecret.yaml | 57 + .../job-patch/job-patchWebhook.yaml | 57 + .../admission-webhooks/job-patch/psp.yaml | 50 + .../admission-webhooks/job-patch/role.yaml | 21 + .../job-patch/rolebinding.yaml | 21 + .../job-patch/serviceaccount.yaml | 14 + .../mutatingWebhookConfiguration.yaml | 32 + .../templates/cert-manager.yaml | 52 + .../templates/clusterrole.yaml | 39 + .../templates/clusterrolebinding.yaml | 26 + .../templates/configmap.yaml | 9 + .../templates/deployment.yaml | 92 + .../templates/secret.yaml | 2 + .../templates/service.yaml | 13 + .../templates/serviceaccount.yaml | 13 + .../tests/deployment_test.yaml | 32 + .../tests/job_patch_psp_test.yaml | 23 + .../tests/job_serviceaccount_test.yaml | 64 + .../tests/rbac_test.yaml | 41 + .../newrelic-infra-operator/values.yaml | 222 + .../newrelic-infrastructure/.helmignore | 1 + .../charts/newrelic-infrastructure/Chart.lock | 6 + .../charts/newrelic-infrastructure/Chart.yaml | 26 + .../charts/newrelic-infrastructure/README.md | 220 + .../newrelic-infrastructure/README.md.gotmpl | 137 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../test-cplane-kind-deployment-values.yaml | 135 + .../ci/test-values.yaml | 134 + .../templates/NOTES.txt | 131 + .../templates/_helpers.tpl | 118 + .../templates/_helpers_compatibility.tpl | 202 + .../templates/clusterrole.yaml | 35 + .../templates/clusterrolebinding.yaml | 16 + .../controlplane/_affinity_helper.tpl | 11 + .../controlplane/_agent-config_helper.tpl | 20 + .../templates/controlplane/_host_network.tpl | 22 + .../templates/controlplane/_naming.tpl | 16 + .../templates/controlplane/_rbac.tpl | 40 + .../controlplane/_tolerations_helper.tpl | 11 + .../controlplane/agent-configmap.yaml | 18 + .../templates/controlplane/clusterrole.yaml | 47 + .../controlplane/clusterrolebinding.yaml | 16 + .../templates/controlplane/daemonset.yaml | 205 + .../templates/controlplane/rolebinding.yaml | 21 + .../controlplane/scraper-configmap.yaml | 36 + .../controlplane/serviceaccount.yaml | 13 + .../templates/ksm/_affinity_helper.tpl | 14 + .../templates/ksm/_agent-config_helper.tpl | 20 + .../templates/ksm/_host_network.tpl | 22 + .../templates/ksm/_naming.tpl | 8 + .../templates/ksm/_tolerations_helper.tpl | 11 + .../templates/ksm/agent-configmap.yaml | 18 + .../templates/ksm/deployment.yaml | 192 + .../templates/ksm/scraper-configmap.yaml | 15 + .../templates/kubelet/_affinity_helper.tpl | 33 + .../kubelet/_agent-config_helper.tpl | 31 + .../templates/kubelet/_host_network.tpl | 22 + .../templates/kubelet/_naming.tpl | 12 + .../kubelet/_security_context_helper.tpl | 32 + .../templates/kubelet/_tolerations_helper.tpl | 11 + .../templates/kubelet/agent-configmap.yaml | 18 + .../templates/kubelet/daemonset.yaml | 265 + .../kubelet/integrations-configmap.yaml | 72 + .../templates/kubelet/scraper-configmap.yaml | 18 + .../templates/podsecuritypolicy.yaml | 26 + .../templates/secret.yaml | 2 + .../templates/serviceaccount.yaml | 13 + .../newrelic-infrastructure/values.yaml | 599 ++ .../newrelic-k8s-metrics-adapter/.helmignore | 25 + .../newrelic-k8s-metrics-adapter/Chart.lock | 6 + .../newrelic-k8s-metrics-adapter/Chart.yaml | 25 + .../newrelic-k8s-metrics-adapter/README.md | 139 + .../README.md.gotmpl | 107 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-values.yaml | 14 + .../templates/_helpers.tpl | 57 + .../templates/adapter-clusterrolebinding.yaml | 14 + .../templates/adapter-rolebinding.yaml | 15 + .../templates/apiservice/apiservice.yaml | 19 + .../apiservice/job-patch/clusterrole.yaml | 26 + .../job-patch/clusterrolebinding.yaml | 19 + .../job-patch/job-createSecret.yaml | 55 + .../job-patch/job-patchAPIService.yaml | 53 + .../templates/apiservice/job-patch/psp.yaml | 49 + .../templates/apiservice/job-patch/role.yaml | 20 + .../apiservice/job-patch/rolebinding.yaml | 20 + .../apiservice/job-patch/serviceaccount.yaml | 18 + .../templates/configmap.yaml | 19 + .../templates/deployment.yaml | 113 + .../templates/hpa-clusterrole.yaml | 15 + .../templates/hpa-clusterrolebinding.yaml | 14 + .../templates/secret.yaml | 10 + .../templates/service.yaml | 13 + .../templates/serviceaccount.yaml | 13 + .../tests/apiservice_test.yaml | 22 + .../tests/common_extra_naming_test.yaml | 27 + .../tests/configmap_test.yaml | 104 + .../tests/deployment_test.yaml | 99 + .../tests/hpa_clusterrolebinding_test.yaml | 18 + .../job_patch_cluster_rolebinding_test.yaml | 22 + .../tests/job_patch_clusterrole_test.yaml | 20 + .../tests/job_patch_common_test.yaml | 27 + .../job_patch_job_createsecret_test.yaml | 47 + .../job_patch_job_patchapiservice_test.yaml | 56 + .../tests/job_serviceaccount_test.yaml | 79 + .../tests/rbac_test.yaml | 50 + .../newrelic-k8s-metrics-adapter/values.yaml | 156 + .../5.0.98/charts/newrelic-logging/Chart.lock | 6 + .../5.0.98/charts/newrelic-logging/Chart.yaml | 20 + .../5.0.98/charts/newrelic-logging/README.md | 268 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 663 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 56 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-enable-windows-values.yaml | 2 + .../ci/test-lowdatamode-values.yaml | 1 + .../ci/test-override-global-lowdatamode.yaml | 3 + .../ci/test-staging-values.yaml | 1 + .../ci/test-with-empty-global.yaml | 1 + .../ci/test-with-empty-values.yaml | 0 ...and-plugin-metrics-dashboard-template.json | 2237 ++++ .../newrelic-logging/templates/NOTES.txt | 18 + .../newrelic-logging/templates/_helpers.tpl | 215 + .../templates/clusterrole.yaml | 23 + .../templates/clusterrolebinding.yaml | 15 + .../newrelic-logging/templates/configmap.yaml | 38 + .../templates/daemonset-windows.yaml | 174 + .../newrelic-logging/templates/daemonset.yaml | 212 + .../templates/persistentvolume.yaml | 57 + .../templates/podsecuritypolicy.yaml | 24 + .../newrelic-logging/templates/secret.yaml | 12 + .../templates/serviceaccount.yaml | 17 + .../tests/cri_parser_test.yaml | 37 + .../tests/dns_config_test.yaml | 62 + .../tests/endpoint_region_selection_test.yaml | 128 + .../fluentbit_k8logging_exclude_test.yaml | 45 + .../tests/fluentbit_persistence_test.yaml | 317 + .../tests/fluentbit_pod_label_test.yaml | 48 + .../tests/fluentbit_sendmetrics_test.yaml | 74 + .../tests/host_network_test.yaml | 46 + .../newrelic-logging/tests/images_test.yaml | 96 + .../tests/linux_volume_mount_test.yaml | 37 + .../newrelic-logging/tests/rbac_test.yaml | 48 + .../charts/newrelic-logging/values.yaml | 361 + .../5.0.98/charts/newrelic-pixie/Chart.yaml | 18 + .../5.0.98/charts/newrelic-pixie/README.md | 166 + .../charts/newrelic-pixie/ci/test-values.yaml | 5 + .../charts/newrelic-pixie/templates/NOTES.txt | 27 + .../newrelic-pixie/templates/_helpers.tpl | 172 + .../newrelic-pixie/templates/configmap.yaml | 12 + .../charts/newrelic-pixie/templates/job.yaml | 165 + .../newrelic-pixie/templates/secret.yaml | 20 + .../newrelic-pixie/tests/configmap.yaml | 44 + .../charts/newrelic-pixie/tests/jobs.yaml | 138 + .../5.0.98/charts/newrelic-pixie/values.yaml | 75 + .../newrelic-prometheus-agent/.helmignore | 23 + .../newrelic-prometheus-agent/Chart.lock | 6 + .../newrelic-prometheus-agent/Chart.yaml | 22 + .../newrelic-prometheus-agent/README.md | 244 + .../README.md.gotmpl | 209 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-values.yaml | 6 + .../static/lowdatamodedefaults.yaml | 6 + .../static/metrictyperelabeldefaults.yaml | 17 + .../templates/_helpers.tpl | 165 + .../templates/clusterrole.yaml | 24 + .../templates/clusterrolebinding.yaml | 16 + .../templates/configmap.yaml | 31 + .../templates/secret.yaml | 2 + .../templates/serviceaccount.yaml | 13 + .../templates/statefulset.yaml | 157 + .../tests/configmap_test.yaml | 572 ++ .../tests/configurator_image_test.yaml | 57 + .../tests/integration_filters_test.yaml | 119 + .../tests/lowdatamode_configmap_test.yaml | 138 + .../newrelic-prometheus-agent/values.yaml | 473 + .../5.0.98/charts/nri-kube-events/Chart.lock | 6 + .../5.0.98/charts/nri-kube-events/Chart.yaml | 26 + .../5.0.98/charts/nri-kube-events/README.md | 79 + .../charts/nri-kube-events/README.md.gotmpl | 43 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-bare-minimum-values.yaml | 3 + .../ci/test-custom-attributes-as-map.yaml | 12 + .../ci/test-custom-attributes-as-string.yaml | 11 + .../nri-kube-events/ci/test-values.yaml | 60 + .../nri-kube-events/templates/NOTES.txt | 3 + .../nri-kube-events/templates/_helpers.tpl | 45 + .../templates/_helpers_compatibility.tpl | 262 + .../templates/agent-configmap.yaml | 12 + .../templates/clusterrole.yaml | 42 + .../templates/clusterrolebinding.yaml | 16 + .../nri-kube-events/templates/configmap.yaml | 23 + .../nri-kube-events/templates/deployment.yaml | 124 + .../nri-kube-events/templates/secret.yaml | 2 + .../templates/serviceaccount.yaml | 11 + .../tests/agent_configmap_test.yaml | 46 + .../nri-kube-events/tests/configmap_test.yaml | 139 + .../tests/deployment_test.yaml | 104 + .../nri-kube-events/tests/images_test.yaml | 168 + .../tests/security_context_test.yaml | 77 + .../5.0.98/charts/nri-kube-events/values.yaml | 135 + .../charts/nri-metadata-injection/.helmignore | 1 + .../charts/nri-metadata-injection/Chart.lock | 6 + .../charts/nri-metadata-injection/Chart.yaml | 25 + .../charts/nri-metadata-injection/README.md | 68 + .../nri-metadata-injection/README.md.gotmpl | 41 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-values.yaml | 5 + .../templates/NOTES.txt | 23 + .../templates/_helpers.tpl | 72 + .../job-patch/clusterrole.yaml | 27 + .../job-patch/clusterrolebinding.yaml | 20 + .../job-patch/job-createSecret.yaml | 61 + .../job-patch/job-patchWebhook.yaml | 61 + .../admission-webhooks/job-patch/psp.yaml | 50 + .../admission-webhooks/job-patch/role.yaml | 21 + .../job-patch/rolebinding.yaml | 21 + .../job-patch/serviceaccount.yaml | 14 + .../mutatingWebhookConfiguration.yaml | 36 + .../templates/cert-manager.yaml | 53 + .../templates/deployment.yaml | 85 + .../templates/service.yaml | 13 + .../tests/cluster_test.yaml | 39 + .../tests/job_serviceaccount_test.yaml | 59 + .../tests/rbac_test.yaml | 38 + .../tests/volume_mounts_test.yaml | 30 + .../charts/nri-metadata-injection/values.yaml | 102 + .../5.0.98/charts/nri-prometheus/.helmignore | 22 + .../5.0.98/charts/nri-prometheus/Chart.lock | 6 + .../5.0.98/charts/nri-prometheus/Chart.yaml | 29 + .../5.0.98/charts/nri-prometheus/README.md | 116 + .../charts/nri-prometheus/README.md.gotmpl | 83 + .../charts/common-library/.helmignore | 23 + .../charts/common-library/Chart.yaml | 17 + .../charts/common-library/DEVELOPERS.md | 747 ++ .../charts/common-library/README.md | 106 + .../common-library/templates/_affinity.tpl | 10 + .../templates/_agent-config.tpl | 26 + .../common-library/templates/_cluster.tpl | 15 + .../templates/_custom-attributes.tpl | 17 + .../common-library/templates/_dnsconfig.tpl | 10 + .../common-library/templates/_fedramp.tpl | 25 + .../common-library/templates/_hostnetwork.tpl | 39 + .../common-library/templates/_images.tpl | 94 + .../common-library/templates/_insights.tpl | 56 + .../templates/_insights_secret.yaml.tpl | 21 + .../common-library/templates/_labels.tpl | 54 + .../common-library/templates/_license.tpl | 68 + .../templates/_license_secret.yaml.tpl | 21 + .../templates/_low-data-mode.tpl | 26 + .../common-library/templates/_naming.tpl | 73 + .../templates/_nodeselector.tpl | 10 + .../templates/_priority-class-name.tpl | 10 + .../common-library/templates/_privileged.tpl | 28 + .../common-library/templates/_proxy.tpl | 10 + .../common-library/templates/_region.tpl | 74 + .../templates/_security-context.tpl | 23 + .../templates/_serviceaccount.tpl | 90 + .../common-library/templates/_staging.tpl | 39 + .../common-library/templates/_tolerations.tpl | 10 + .../common-library/templates/_userkey.tpl | 56 + .../templates/_userkey_secret.yaml.tpl | 21 + .../common-library/templates/_verbose-log.tpl | 54 + .../charts/common-library/values.yaml | 1 + .../ci/test-lowdatamode-values.yaml | 9 + .../ci/test-override-global-lowdatamode.yaml | 10 + .../charts/nri-prometheus/ci/test-values.yaml | 104 + .../static/lowdatamodedefaults.yaml | 10 + .../nri-prometheus/templates/_helpers.tpl | 15 + .../nri-prometheus/templates/clusterrole.yaml | 23 + .../templates/clusterrolebinding.yaml | 16 + .../nri-prometheus/templates/configmap.yaml | 21 + .../nri-prometheus/templates/deployment.yaml | 98 + .../nri-prometheus/templates/secret.yaml | 2 + .../templates/serviceaccount.yaml | 13 + .../nri-prometheus/tests/configmap_test.yaml | 86 + .../nri-prometheus/tests/deployment_test.yaml | 82 + .../nri-prometheus/tests/labels_test.yaml | 32 + .../5.0.98/charts/nri-prometheus/values.yaml | 251 + .../charts/pixie-operator-chart/Chart.yaml | 4 + .../pixie-operator-chart/crds/olm_crd.yaml | 9045 +++++++++++++++++ .../pixie-operator-chart/crds/vizier_crd.yaml | 347 + .../templates/00_olm.yaml | 232 + .../templates/01_px_olm.yaml | 13 + .../templates/02_catalog.yaml | 37 + .../templates/03_subscription.yaml | 11 + .../templates/04_vizier.yaml | 100 + .../templates/deleter.yaml | 25 + .../templates/deleter_role.yaml | 77 + .../charts/pixie-operator-chart/values.yaml | 75 + .../nri-bundle/5.0.98/ci/test-values.yaml | 21 + .../nri-bundle/5.0.98/questions.yaml | 113 + .../new-relic/nri-bundle/5.0.98/values.yaml | 169 + .../speedscale-operator/2.2.606/.helmignore | 23 + .../speedscale-operator/2.2.606/Chart.yaml | 27 + .../speedscale-operator/2.2.606/LICENSE | 201 + .../speedscale-operator/2.2.606/README.md | 111 + .../speedscale-operator/2.2.606/app-readme.md | 111 + .../2.2.606/questions.yaml | 9 + .../2.2.606/templates/NOTES.txt | 12 + .../2.2.606/templates/admission.yaml | 209 + .../2.2.606/templates/configmap.yaml | 43 + .../templates/crds/trafficreplays.yaml | 525 + .../2.2.606/templates/deployments.yaml | 132 + .../2.2.606/templates/hooks.yaml | 73 + .../2.2.606/templates/rbac.yaml | 244 + .../2.2.606/templates/secrets.yaml | 18 + .../2.2.606/templates/services.yaml | 22 + .../2.2.606/templates/tls.yaml | 183 + .../speedscale-operator/2.2.606/values.yaml | 138 + index.yaml | 192 +- 899 files changed, 93411 insertions(+), 1 deletion(-) create mode 100644 assets/jfrog/artifactory-ha-107.98.7.tgz create mode 100644 assets/jfrog/artifactory-jcr-107.98.7.tgz create mode 100644 assets/new-relic/nri-bundle-5.0.98.tgz create mode 100644 assets/speedscale/speedscale-operator-2.2.606.tgz create mode 100644 charts/jfrog/artifactory-ha/107.98.7/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.98.7/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/Chart.lock create mode 100644 charts/jfrog/artifactory-ha/107.98.7/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/LICENSE create mode 100644 charts/jfrog/artifactory-ha/107.98.7/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/app-readme.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/Chart.lock create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/.helmignore create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/Chart.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_affinities.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_errors.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_images.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_ingress.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_labels.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_names.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_storage.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_utils.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_cassandra.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_mariadb.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_mongodb.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_postgresql.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_redis.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_validations.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/commonAnnotations.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/shmvolume-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/conf.d/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/docker-entrypoint-initdb.d/README.md create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/extended-config-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/extra-list.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/initialization-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/metrics-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/metrics-svc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/prometheusrule.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/role.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/rolebinding.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/servicemonitor.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/statefulset-readreplicas.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc-headless.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc-read.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/values.schema.json create mode 100644 charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/global-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/large-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/loggers-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/medium-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/migration-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/nginx-autoreload-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/rtsplit-access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/rtsplit-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/small-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/ci/test-values.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/binarystore.xml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/installer-info.json create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/migrate.sh create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/migrationHelmInfo.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/migrationStatus.sh create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/nginx-main-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/files/system.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/logo/artifactory-logo.png create mode 100644 charts/jfrog/artifactory-ha/107.98.7/questions.yml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-2xlarge.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-large.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-medium.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-small.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xlarge.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xsmall.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/_system-yaml-render.tpl create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/additional-resources.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/admin-bootstrap-creds.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-access-config.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-binarystore-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-configmaps.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-custom-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-database-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-gcp-credentials-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-installer-info.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-license-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-migration-scripts.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-nfs-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-node-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-node-statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-primary-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-primary-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-primary-statefulset.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-priority-class.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-role.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-rolebinding.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-secrets.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-service-grpc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-storage-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-system-yaml.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-unified-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/filebeat-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/ingress-grpc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/ingress.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/logger-configmap.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-certificate-secret.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-deployment.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-pdb.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-pvc.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-scripts-conf.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/templates/nginx-service.yaml create mode 100644 charts/jfrog/artifactory-ha/107.98.7/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/LICENSE create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/app-readme.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/CHANGELOG.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/Chart.lock create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/LICENSE create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/Chart.lock create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/.helmignore create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/Chart.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/conf.d/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/extra-list.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/role.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/rolebinding.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/statefulset.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc-headless.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc-read.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/values.schema.json create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/derby-test-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/global-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/large-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/loggers-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/medium-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/migration-disabled-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/nginx-autoreload-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/rtsplit-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/small-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/test-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/binarystore.xml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/installer-info.json create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/migrate.sh create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/migrationHelmInfo.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/migrationStatus.sh create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/nginx-main-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/system.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/logo/artifactory-logo.png create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-2xlarge.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-large.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-medium.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-small.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xlarge.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xsmall.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_helpers.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_system-yaml-render.tpl create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/additional-resources.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/admin-bootstrap-creds.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-access-config.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-binarystore-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-configmaps.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-custom-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-database-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-hpa.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-installer-info.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-license-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-migration-scripts.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-networkpolicy.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-nfs-pvc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-pdb.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-priority-class.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-role.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-rolebinding.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-secrets.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-service-grpc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-service.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-serviceaccount.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-statefulset.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-system-yaml.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-unified-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/filebeat-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/ingress-grpc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/ingress.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/logger-configmap.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-artifactory-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-certificate-secret.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-deployment.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-pdb.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-pvc.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-scripts-conf.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-service.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/ci/default-values.yaml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/logo/jcr-logo.png create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/questions.yml create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/templates/NOTES.txt create mode 100644 charts/jfrog/artifactory-jcr/107.98.7/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/app-readme.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_tls.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/certmanager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/instrumentation-crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/leader-election-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/manager-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/proxy-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/reader-rbac.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-configuration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/cert_manager_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/webhook_ssl_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/crs-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/extra-manifests.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/kubeconfig-secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/networkpolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/pdb.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rbac-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/servicemonitor.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/cert-manager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-enable-windows-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-lowdatamode-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-staging-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-global.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset-windows.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/persistentvolume.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/podsecuritypolicy.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/cri_parser_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/dns_config_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/host_network_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/images_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/linux_volume_mount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/job.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/jobs.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/statefulset.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-bare-minimum-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers_compatibility.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/agent-configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/agent_configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/images_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/security_context_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/NOTES.txt create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/cert-manager.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/service.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/cluster_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/rbac_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/volume_mounts_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.lock create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md.gotmpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/.helmignore create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/DEVELOPERS.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/README.md create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_images.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_labels.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_naming.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_region.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_staging.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey_secret.yaml.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-lowdatamode-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/static/lowdatamodedefaults.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/_helpers.tpl create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrole.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrolebinding.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/configmap.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/deployment.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/secret.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/serviceaccount.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/configmap_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/deployment_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/labels_test.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/Chart.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/olm_crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/vizier_crd.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/00_olm.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/01_px_olm.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/02_catalog.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/03_subscription.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/04_vizier.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter_role.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/ci/test-values.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/questions.yaml create mode 100644 charts/new-relic/nri-bundle/5.0.98/values.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/.helmignore create mode 100644 charts/speedscale/speedscale-operator/2.2.606/Chart.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/LICENSE create mode 100644 charts/speedscale/speedscale-operator/2.2.606/README.md create mode 100644 charts/speedscale/speedscale-operator/2.2.606/app-readme.md create mode 100644 charts/speedscale/speedscale-operator/2.2.606/questions.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/NOTES.txt create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/admission.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/configmap.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/crds/trafficreplays.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/deployments.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/hooks.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/rbac.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/secrets.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/services.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/templates/tls.yaml create mode 100644 charts/speedscale/speedscale-operator/2.2.606/values.yaml diff --git a/assets/jfrog/artifactory-ha-107.98.7.tgz b/assets/jfrog/artifactory-ha-107.98.7.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b5d0bf7f8da28a828089517880d945148fd3344c GIT binary patch literal 219954 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°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
` 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
LFp^$O4pY4j*X zKSt?~F)=X(C)vn9u|C)-#K=ep&+eSXni*fKK0{BeP*W2fS2)HaZA`r8Qqp_;9P1pR zI$|@-0V6Ud{L!n^AnY#E9+W%Y^)X7e9&B%)JU=wrNdD6ec|u!@ws=&3b2`jLB7pY){Vd3H2_=kvr1A#O?KT$|J zGXffIIwGn69Me&TVq^xtJkzM8rNfdUn5Dq{^l9-62BhYDF^Uq+bVX_+9E);EDE$vv zVAsRODAUPmEf7=9pB9bNuY_HE%LqVy=sxu6sMPL0ogPk>{y)cR30b;JxSwtHvy5B| z$7aQAGIFkoVTJl9Yk^+E(=DCLG%i2}tiYGm<1L6>IpuV%126eDPli{=R?g(M|2ynF zpbB~s&qv*DLw@wsOY+t<-d}N2N6*DR_$uR>*qwvIWHfGDr)2S*@Of$kY*&zBemQ9hRwH?F5uymEnLe%V{tX_Xayv(f|>L zBB(WREXvhc8CO0*e~gInZ*+#z?ox<+*C?f!!k(j|=JJ%+Jrio(W$(NT0!KbZQRbCo z5NW9(Rd0+1iSkV~dDMTLFw*_U6}%&7m#xKi|Q4(G+=t3Y(Q$ zA{N5VivNMP+5K?yv_YGso)t2=tz3L>IF$K#h`~^2 z*)#Okg|h9*6R~c8@}r5ynX7%g|M;ZKU~sOEw%8hfkf{rM6!$d|InQyN0`1eaz}1Ai zObQmVk#JX_&t|??p`n+o+_p#aRR=V-%jHA(QUFQ%Zn7 zn+*0K6b;mV*M8S(mE=rYXKB8)<@F2aXdu;v8((6j8_+OA;fZ0{kvp2eTZmRY{bR22 z$GT|rM~|<5zPa~};iQiD6**EEsX$IvTtv83z{JS73>vzg8f%zg#66tBL! z*AU8o9CZl8T_%102SyB8Ek=?b9f&bH`p#c_ODwB^qU0!i1^F_FnSP+++{TKSuA=t^ zJ;3o`<$*XARXY-a+p}d#uneJ}ohY!1eX3)NL>ES)ciwVyQmhG_&U(J%#3?4d4 zYX8Tpqv!j)zP_XQ!`$14_ip^jKm;I>;QSIJb7IoQ`jt|$5)@8U;47c04g^uJ{djeF z6>Oj6z`VVk1sLs;-9Zxy?>CYr}H*)W#%TrTedKo$TPB2i5 z!_xhO&u+HiA0=;f{Y)sY`xv_S##k><{1u^8xRbKeSXuEaOppvKyi^;c!+5cZl2ES* z%#1N|qKJlybyvynpf17!U->kp)OXZ2TR+CpllS|!oNf8I0h!@8 zYwB8jrLKJEa}oEUWFHoa7~ryuC-dH=1mZ^g*C2cNKbteuZh7(4_X zpD?($UZPf!#<3{pc`T*CjhhrFLDwD6zxVAye=X%do~?av8M{K|&6{C3r1Sm%P$cN6 z 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

=HPrXNr@@;(+#s0l2IkVnn`~P|wfm*Q& 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 ALjV8( literal 0 HcmV?d00001 diff --git a/assets/jfrog/artifactory-jcr-107.98.7.tgz b/assets/jfrog/artifactory-jcr-107.98.7.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d8544c0b07bf4750f21cdb525c8048f3139b02b9 GIT binary patch literal 461951 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

yPCYVu>3rzn6#6}O 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 ytUrM!p1bJ3&nZOHNpC0E2m4wKWvmS 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 literal 0 HcmV?d00001 diff --git a/assets/new-relic/nri-bundle-5.0.98.tgz b/assets/new-relic/nri-bundle-5.0.98.tgz new file mode 100644 index 0000000000000000000000000000000000000000..67108d3275fb6292f9d33e30ef9d2bf4e0b7b84d GIT binary patch literal 366915 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ʚ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

bct>3TU;lKaG7^o5cA%M)&+xqkER| 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 P00960sOUKe0N^PA;x$7) literal 0 HcmV?d00001 diff --git a/assets/speedscale/speedscale-operator-2.2.606.tgz b/assets/speedscale/speedscale-operator-2.2.606.tgz new file mode 100644 index 0000000000000000000000000000000000000000..cae14c4bbf8bc829ad26b8b71029b14e9e8b5cff GIT binary patch literal 17062 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 V?jQI3?*IS*|NpSkIs5=r0RSk)as2=Q literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-ha/107.98.7/.helmignore b/charts/jfrog/artifactory-ha/107.98.7/.helmignore new file mode 100644 index 000000000..b6e97f07f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/.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.98.7/CHANGELOG.md b/charts/jfrog/artifactory-ha/107.98.7/CHANGELOG.md new file mode 100644 index 000000000..aaf55c01e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/CHANGELOG.md @@ -0,0 +1,1493 @@ +# JFrog Artifactory-ha Chart Changelog +All changes to this chart will be documented in this file + +## [107.98.7] - Oct 02, 2024 +* Add support for `extraEnvironmentVariables` on filebeat Sidecar [GH-1377](https://github.com/jfrog/charts/pull/1377) +* Support for SSL offload HTTPS proto override in Nginx service (ClusterIP, LoadBalancer) layer. Introduced `nginx.service.ssloffloadForceHttps` field with boolean type. [GH-1906](https://github.com/jfrog/charts/pull/1906) +* Enable Access workers integration when artifactory.worker.enabled is true +* Added `signedUrlExpirySeconds` option to artifactory.persistence.type of `google-storage`, `google-storage-v2`, and `google-storage-v2-direct` [GH-1858](https://github.com/jfrog/charts/pull/1858) +* Added support to bootstrap jfconnect custom certs to jfconnect trusted directory + +## [107.96.0] - Sep 18, 2024 +* Merged Artifactory sizing templates to a single file per size +* Added metadata and observability standalone image support + +## [107.95.0] - Aug 26, 2024 +* Adding missing annotations to primary Artifactory Service [GH-1862](https://github.com/jfrog/charts/pull/1862) + +## [107.94.0] - Aug 14, 2024 +* Fixed #Expose rtfs port only when it is enabled + +## [107.93.0] - Aug 9, 2024 +* Added support for worker via `artifactory.worker.enabled` flag +* Fixed creation of duplicate objects in statefulSet + +## [107.92.0] - July 31, 2024 +* Updating the example link for downloading the DB driver +* Adding dedicated ingress and service path for GRPC protocol + +## [107.91.0] - July 18, 2024 +* Remove X-JFrog-Override-Base-Url port when using default `443/80` ports + +## [107.90.0] - 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` +* 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.98.7/Chart.lock b/charts/jfrog/artifactory-ha/107.98.7/Chart.lock new file mode 100644 index 000000000..eb9409971 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/Chart.yaml b/charts/jfrog/artifactory-ha/107.98.7/Chart.yaml new file mode 100644 index 000000000..5589ffccd --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/Chart.yaml @@ -0,0 +1,32 @@ +annotations: + artifactoryServiceVersion: 7.98.6 + 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 + metadataVersion: 7.90.0 + observabilityVersion: 1.31.5 +apiVersion: v2 +appVersion: 7.98.7 +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: 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.98.7 diff --git a/charts/jfrog/artifactory-ha/107.98.7/LICENSE b/charts/jfrog/artifactory-ha/107.98.7/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/README.md b/charts/jfrog/artifactory-ha/107.98.7/README.md new file mode 100644 index 000000000..e0ef54016 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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. 3 primary 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.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.98.7/app-readme.md b/charts/jfrog/artifactory-ha/107.98.7/app-readme.md new file mode 100644 index 000000000..a5aa5fd47 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/.helmignore b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/Chart.lock b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/Chart.lock new file mode 100644 index 000000000..3687f52df --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/Chart.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/Chart.yaml new file mode 100644 index 000000000..4b197b207 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/README.md b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/README.md new file mode 100644 index 000000000..63d3605bb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/.helmignore b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/Chart.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/Chart.yaml new file mode 100644 index 000000000..bcc3808d0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/README.md b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/README.md new file mode 100644 index 000000000..7287cbb5f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_affinities.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_affinities.tpl new file mode 100644 index 000000000..493a6dc7e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_capabilities.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100644 index 000000000..4dde56a38 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_errors.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100644 index 000000000..a79cc2e32 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_images.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_images.tpl new file mode 100644 index 000000000..60f04fd6e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_ingress.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_ingress.tpl new file mode 100644 index 000000000..622ef50e3 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_labels.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100644 index 000000000..252066c7e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_names.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_names.tpl new file mode 100644 index 000000000..adf2a74f4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_secrets.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100644 index 000000000..60b84a701 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_storage.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100644 index 000000000..60e2a844f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_tplvalues.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 000000000..2db166851 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_utils.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100644 index 000000000..ea083a249 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/_warnings.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100644 index 000000000..ae10fa41e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_cassandra.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 000000000..8679ddffb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_mariadb.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 000000000..bb5ed7253 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_mongodb.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 000000000..7d5ecbccb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_postgresql.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 000000000..992bcd390 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_redis.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 000000000..3e2a47c03 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/templates/validations/_validations.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 000000000..9a814cf40 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/charts/common/values.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/charts/common/values.yaml new file mode 100644 index 000000000..9ecdc93f5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/ci/commonAnnotations.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/commonAnnotations.yaml new file mode 100644 index 000000000..97e18a4cc --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/ci/default-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/default-values.yaml new file mode 100644 index 000000000..fc2ba605a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/ci/shmvolume-disabled-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/shmvolume-disabled-values.yaml new file mode 100644 index 000000000..347d3b40a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/ci/shmvolume-disabled-values.yaml @@ -0,0 +1,2 @@ +shmVolume: + enabled: false diff --git a/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/README.md b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/README.md new file mode 100644 index 000000000..1813a2fea --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/files/conf.d/README.md b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/conf.d/README.md new file mode 100644 index 000000000..184c1875d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/files/docker-entrypoint-initdb.d/README.md b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/files/docker-entrypoint-initdb.d/README.md new file mode 100644 index 000000000..cba38091e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/NOTES.txt b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/NOTES.txt new file mode 100644 index 000000000..4e98958c1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/_helpers.tpl b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 000000000..1f98efe78 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/configmap.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/configmap.yaml new file mode 100644 index 000000000..3a5ea18ae --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/extended-config-configmap.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/extended-config-configmap.yaml new file mode 100644 index 000000000..b0dad253b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/extra-list.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/extra-list.yaml new file mode 100644 index 000000000..9ac65f9e1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/initialization-configmap.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/initialization-configmap.yaml new file mode 100644 index 000000000..7796c67a9 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/metrics-configmap.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/metrics-configmap.yaml new file mode 100644 index 000000000..fa539582b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/metrics-svc.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/metrics-svc.yaml new file mode 100644 index 000000000..af8b67e2f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/networkpolicy.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/networkpolicy.yaml new file mode 100644 index 000000000..4f2740ea0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/podsecuritypolicy.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..0c49694fa --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/prometheusrule.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/prometheusrule.yaml new file mode 100644 index 000000000..d0f408c78 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/role.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/role.yaml new file mode 100644 index 000000000..017a5716b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/rolebinding.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/rolebinding.yaml new file mode 100644 index 000000000..189775a15 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/secrets.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/secrets.yaml new file mode 100644 index 000000000..d492cd593 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/serviceaccount.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/serviceaccount.yaml new file mode 100644 index 000000000..03f0f50e7 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/servicemonitor.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/servicemonitor.yaml new file mode 100644 index 000000000..587ce85b8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/statefulset-readreplicas.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/statefulset-readreplicas.yaml new file mode 100644 index 000000000..b038299bf --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/statefulset.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 000000000..f8163fd99 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/svc-headless.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc-headless.yaml new file mode 100644 index 000000000..6f5f3b9ee --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/svc-read.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc-read.yaml new file mode 100644 index 000000000..56195ea1e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/templates/svc.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/templates/svc.yaml new file mode 100644 index 000000000..a29431b6a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/values.schema.json b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/values.schema.json new file mode 100644 index 000000000..66a2a9dd0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/charts/postgresql/values.yaml b/charts/jfrog/artifactory-ha/107.98.7/charts/postgresql/values.yaml new file mode 100644 index 000000000..82ce09234 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/access-tls-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/access-tls-values.yaml new file mode 100644 index 000000000..27e24d346 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/default-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/default-values.yaml new file mode 100644 index 000000000..020f52335 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/global-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/global-values.yaml new file mode 100644 index 000000000..0987e17ca --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/large-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/large-values.yaml new file mode 100644 index 000000000..153307aa2 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/loggers-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/loggers-values.yaml new file mode 100644 index 000000000..03c94be95 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/medium-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/medium-values.yaml new file mode 100644 index 000000000..115e7d460 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/migration-disabled-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/migration-disabled-values.yaml new file mode 100644 index 000000000..44895a373 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/nginx-autoreload-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/nginx-autoreload-values.yaml new file mode 100644 index 000000000..a6f4e8001 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/rtsplit-access-tls-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/rtsplit-access-tls-values.yaml new file mode 100644 index 000000000..6f3b13cb1 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/rtsplit-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/rtsplit-values.yaml new file mode 100644 index 000000000..87832a505 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/small-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/small-values.yaml new file mode 100644 index 000000000..b4557289e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/ci/test-values.yaml b/charts/jfrog/artifactory-ha/107.98.7/ci/test-values.yaml new file mode 100644 index 000000000..8bbbb5b3e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/files/binarystore.xml b/charts/jfrog/artifactory-ha/107.98.7/files/binarystore.xml new file mode 100644 index 000000000..fca04f08a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/files/binarystore.xml @@ -0,0 +1,444 @@ +{{- 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 }} + {{- if .Values.artifactory.persistence.googleStorage.signedUrlExpirySeconds }} + true + {{- else }} + false + {{- end }} + + +{{- 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.98.7/files/installer-info.json b/charts/jfrog/artifactory-ha/107.98.7/files/installer-info.json new file mode 100644 index 000000000..cf6b020fb --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/files/migrate.sh b/charts/jfrog/artifactory-ha/107.98.7/files/migrate.sh new file mode 100644 index 000000000..ba44160f4 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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}} + {{- if .Values.nginx.service.ssloffloadForceHttps}} + proxy_set_header X-JFrog-Override-Base-Url https://$host; + proxy_set_header X-Forwarded-Proto https; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- end }} + {{- else if or (eq (int .Values.nginx.https.internalPort) 80) (eq (int .Values.nginx.https.externalPort) 443)}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$host_port; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- end }} + 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.98.7/files/nginx-main-conf.yaml b/charts/jfrog/artifactory-ha/107.98.7/files/nginx-main-conf.yaml new file mode 100644 index 000000000..78cecea6a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/files/system.yaml b/charts/jfrog/artifactory-ha/107.98.7/files/system.yaml new file mode 100644 index 000000000..5cac766d5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/files/system.yaml @@ -0,0 +1,174 @@ +router: + serviceRegistry: + insecure: {{ .Values.router.serviceRegistry.insecure }} +shared: +{{- if .Values.artifactory.coldStorage.enabled }} + jfrogColdStorage: + coldInstanceEnabled: true +{{- end }} +{{- if .Values.artifactory.worker.enabled }} + featureToggler: + worker: 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 }} +{{- if .Values.artifactory.worker.enabled }} + -Dartifactory.workers.addon.support=true +{{- end }} + {{- 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.artifactory.worker.enabled }} + worker: + enabled: true +{{- 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.98.7/logo/artifactory-logo.png b/charts/jfrog/artifactory-ha/107.98.7/logo/artifactory-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6c23c5a7f87edaf49f883ffa99d875073e2285 GIT binary patch literal 82419 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 z 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 GZv9`RA2Y%L literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-ha/107.98.7/questions.yml b/charts/jfrog/artifactory-ha/107.98.7/questions.yml new file mode 100644 index 000000000..14e9024e6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/sizing/artifactory-2xlarge.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-2xlarge.yaml new file mode 100644 index 000000000..a6915d470 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-2xlarge.yaml @@ -0,0 +1,160 @@ +############################################################################################## +# The 2xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: "2" + memory: 2Gi + limits: + # cpu: "12" + memory: 4Gi + +frontend: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 200 + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 2Gi + +event: + resources: + requests: + cpu: 200m + memory: 100Mi + limits: + # cpu: "1" + memory: 500Mi + +access: + tomcat: + connector: + maxThreads: 200 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 200 + 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.98.7/sizing/artifactory-large.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-large.yaml new file mode 100644 index 000000000..63a8c6ce5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-large.yaml @@ -0,0 +1,160 @@ +############################################################################################## +# The large sizing +# This size is intended for large organizations. It can be increased with adding replicas or moving to the large sizing +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "8" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 125 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 100 + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 3Gi + +router: + resources: + requests: + cpu: 400m + memory: 800Mi + limits: + # cpu: "8" + memory: 2Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 100 + 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.98.7/sizing/artifactory-medium.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-medium.yaml new file mode 100644 index 000000000..89f9c9e46 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-medium.yaml @@ -0,0 +1,160 @@ +#################################################################################### +# The medium sizing +# This size is just 2 replicas of the small size. Vertical sizing of all services is not changed +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +#################################################################################### +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "2" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + database: + maxOpenConnections: 50 + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +event: + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + # cpu: 500m + memory: 250Mi + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + 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.98.7/sizing/artifactory-small.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-small.yaml new file mode 100644 index 000000000..454f159e5 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-small.yaml @@ -0,0 +1,159 @@ +############################################################################################## +# The small sizing +# This is the size recommended for running Artifactory for small teams +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "2" + memory: 1Gi + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + 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: + database: + maxOpenConnections: 50 + 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.98.7/sizing/artifactory-xlarge.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xlarge.yaml new file mode 100644 index 000000000..f2fe6e952 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xlarge.yaml @@ -0,0 +1,159 @@ +############################################################################################## +# The xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + podAntiAffinity: + type: "hard" + + resources: + requests: + cpu: "2" + memory: 14Gi + limits: + # cpu: "14" + memory: 16Gi + + 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 + # Require multiple Artifactory pods to run on separate nodes + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 150 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 150 + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 4Gi + +router: + resources: + requests: + cpu: 400m + memory: 1Gi + limits: + # cpu: "8" + memory: 2Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 150 + 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.98.7/sizing/artifactory-xsmall.yaml b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xsmall.yaml new file mode 100644 index 000000000..c89a87a54 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/sizing/artifactory-xsmall.yaml @@ -0,0 +1,161 @@ +############################################################################################## + +# The xsmall sizing +# This is the minimum size recommended for running Artifactory +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 15 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 15 + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 50m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + database: + maxOpenConnections: 15 + 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.98.7/templates/NOTES.txt b/charts/jfrog/artifactory-ha/107.98.7/templates/NOTES.txt new file mode 100644 index 000000000..30dfab8b8 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/_helpers.tpl b/charts/jfrog/artifactory-ha/107.98.7/templates/_helpers.tpl new file mode 100644 index 000000000..ec9ba34df --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/templates/_helpers.tpl @@ -0,0 +1,589 @@ +{{/* 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 and (eq $indexReference "metadata") (hasKey $dot.Values.metadata "standaloneImageEnabled") }} + {{- if default false $dot.Values.metadata.standaloneImageEnabled }} + {{- $tag = default $dot.Chart.Annotations.metadataVersion (index $dot.Values $indexReference "image" "tag") | toString -}} + {{- end -}} +{{- end -}} +{{- if and (eq $indexReference "observability") (hasKey $dot.Values.observability "standaloneImageEnabled") }} + {{- if default false $dot.Values.observability.standaloneImageEnabled }} + {{- $tag = default $dot.Chart.Annotations.observabilityVersion (index $dot.Values $indexReference "image" "tag") | toString -}} + {{- end -}} +{{- 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 -}} + +{{/* +Create a default fully qualified grpc ingress name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "artifactory.ingressGrpc.fullname" -}} +{{- printf "%s-%s" (include "artifactory-ha.fullname" .) .Values.ingressGrpc.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified grpc service name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "artifactory.serviceGrpc.fullname" -}} +{{- printf "%s-%s" (include "artifactory-ha.fullname" .) .Values.artifactory.serviceGrpc.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-ha/107.98.7/templates/_system-yaml-render.tpl b/charts/jfrog/artifactory-ha/107.98.7/templates/_system-yaml-render.tpl new file mode 100644 index 000000000..deaa773ea --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/additional-resources.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/additional-resources.yaml new file mode 100644 index 000000000..c4d06f08a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/templates/additional-resources.yaml @@ -0,0 +1,3 @@ +{{ if .Values.additionalResources }} +{{ tpl .Values.additionalResources . }} +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.98.7/templates/admin-bootstrap-creds.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/admin-bootstrap-creds.yaml new file mode 100644 index 000000000..40d91f75e --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-access-config.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-access-config.yaml new file mode 100644 index 000000000..0b96a337d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-binarystore-secret.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-binarystore-secret.yaml new file mode 100644 index 000000000..6824fe90f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-configmaps.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-configmaps.yaml new file mode 100644 index 000000000..1385bc578 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-custom-secrets.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-custom-secrets.yaml new file mode 100644 index 000000000..8065fe686 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-database-secrets.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-database-secrets.yaml new file mode 100644 index 000000000..6daf5db7b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-gcp-credentials-secret.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-gcp-credentials-secret.yaml new file mode 100644 index 000000000..d90769595 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-installer-info.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-installer-info.yaml new file mode 100644 index 000000000..0dff9dc86 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-license-secret.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-license-secret.yaml new file mode 100644 index 000000000..0018fa044 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-license-secret.yaml @@ -0,0 +1,16 @@ +{{ if and (not .Values.artifactory.unifiedSecretInstallation) (not .Values.artifactory.license.secret) }} +{{- 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.98.7/templates/artifactory-migration-scripts.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-migration-scripts.yaml new file mode 100644 index 000000000..fe40f980f --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-networkpolicy.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-networkpolicy.yaml new file mode 100644 index 000000000..9924448f0 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-nfs-pvc.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-nfs-pvc.yaml new file mode 100644 index 000000000..6ed7d82f6 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/artifactory-node-pdb.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/artifactory-node-pdb.yaml new file mode 100644 index 000000000..46c6dac21 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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 .Values.jfconnect.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 }} + 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 }} + {{- if .Values.federation.enabled }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + 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 + {{- if .Values.federation.enabled }} + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + {{- 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 .Values.jfconnect.customCertificatesSecretName }} + echo "Copying custom certificates to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys; + cp -fv /tmp/etc/tls.crt {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.cer {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.pem {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.key {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + {{- 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 .Values.jfconnect.customCertificatesSecretName }} + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.crt" + subPath: tls.crt + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.pem" + subPath: tls.pem + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.cer" + subPath: tls.cer + - name: jfconnect-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 }} + 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' + {{- if default false .Values.metadata.standaloneImageEnabled }} + - exec /opt/jfrog/metadata/app/metadata/bin/jf-metadata + {{- else }} + - exec /opt/jfrog/artifactory/app/metadata/bin/jf-metadata start + {{- end }} + {{- 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 + {{- if default false .Values.metadata.standaloneImageEnabled }} + mountPath: {{ .Values.metadata.persistence.mountPath | quote }} + {{- else }} + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + {{- end }} + 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 }} + {{- if .Values.federation.enabled }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + 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' + {{- if default false .Values.observability.standaloneImageEnabled }} + - exec /opt/jfrog/observability/app/bin/entrypoint-observability.sh + {{- else }} + - exec /opt/jfrog/artifactory/app/observability/bin/jf-observability start + {{- end }} + {{- 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 + {{- if default false .Values.observability.standaloneImageEnabled }} + mountPath: {{ .Values.observability.persistence.mountPath | quote }} + {{- else }} + mountPath: {{ .Values.artifactory.persistence.mountPath | quote }} + {{- end }} + 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 + {{- if .Values.federation.enabled }} + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + {{- 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.ingressGrpc.className }} + {{- end }} + {{- if .Values.ingressGrpc.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.ingressGrpc.hosts }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + {{- range $host := .Values.ingressGrpc.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingressGrpc.grpcPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end }} + {{- else }} + {{- range $host := .Values.ingressGrpc.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingressGrpc.grpcPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} + {{- end }} +{{- end -}} + {{- with .Values.ingressGrpc.additionalRules }} +{{ tpl . $ | indent 2 }} + {{- end }} + + {{- if .Values.ingressGrpc.tls }} + tls: +{{ toYaml .Values.ingressGrpc.tls | indent 4 }} + {{- end -}} + +{{- end -}} diff --git a/charts/jfrog/artifactory-ha/107.98.7/templates/ingress.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/ingress.yaml new file mode 100644 index 000000000..70080a614 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/templates/ingress.yaml @@ -0,0 +1,106 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := include "artifactory-ha.fullname" . -}} +{{- $servicePort := .Values.artifactory.externalPort -}} +{{- $artifactoryServicePort := .Values.artifactory.externalArtifactoryPort -}} +{{- $ingressName := default ( include "artifactory-ha.fullname" . ) .Values.ingress.name -}} +{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} +apiVersion: networking.k8s.io/v1 +{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $ingressName }} + labels: + app: {{ template "artifactory-ha.name" . }} + chart: {{ template "artifactory-ha.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ .Values.ingress.labels | toYaml | trimSuffix "\n"| indent 4 -}} +{{- end}} +{{- if .Values.ingress.annotations }} + annotations: +{{ .Values.ingress.annotations | toYaml | trimSuffix "\n" | indent 4 -}} +{{- end }} +spec: + {{- if and .Values.ingress.className (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.98.7/templates/logger-configmap.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/logger-configmap.yaml new file mode 100644 index 000000000..d3597905d --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/nginx-artifactory-conf.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/nginx-artifactory-conf.yaml new file mode 100644 index 000000000..97ae5f27b --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/nginx-certificate-secret.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/nginx-certificate-secret.yaml new file mode 100644 index 000000000..29c77ad5a --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/nginx-conf.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/nginx-conf.yaml new file mode 100644 index 000000000..4f0d65f25 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/nginx-deployment.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/nginx-deployment.yaml new file mode 100644 index 000000000..d43689b8c --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.98.7/templates/nginx-pdb.yaml b/charts/jfrog/artifactory-ha/107.98.7/templates/nginx-pdb.yaml new file mode 100644 index 000000000..0aed99368 --- /dev/null +++ b/charts/jfrog/artifactory-ha/107.98.7/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.135.1 + 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 + ## Support for workers + ## set 'worker.enabled' to 'true' to enable artifactory addon that executes workers on artifactory events + worker: + 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 + # signedUrlExpirySeconds: 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 + serviceGrpc: + name: grpc + 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: {} + annotations: {} + 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 " --cert=tls.crt --key=tls.key` + # customCertificatesSecretName: + + ## 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.jfconnect.internalPort }}/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.ssloffloadForceHttps Only enabled when service.ssloffload is set to True. + ## Force all requests from NGINX to the upstream server are over HTTPS, even when SSL offloading is enabled. + ## This is useful in environments where internal traffic must remain secure with https only. + ssloffloadForceHttps: 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 }}"] + extraEnvironmentVariables: {} + # - name: MY_ENV_VAR + # value: "" +## 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.98.7/CHANGELOG.md b/charts/jfrog/artifactory-jcr/107.98.7/CHANGELOG.md new file mode 100644 index 000000000..9d3a2d71d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/CHANGELOG.md @@ -0,0 +1,206 @@ +# JFrog Container Registry Chart Changelog +All changes to this chart will be documented in this file. + +## [107.98.7] - 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.98.7/Chart.yaml b/charts/jfrog/artifactory-jcr/107.98.7/Chart.yaml new file mode 100644 index 000000000..9c15e4ae2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7 +dependencies: +- name: artifactory + repository: file://charts/artifactory + version: 107.98.7 +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.98.7 diff --git a/charts/jfrog/artifactory-jcr/107.98.7/LICENSE b/charts/jfrog/artifactory-jcr/107.98.7/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/README.md b/charts/jfrog/artifactory-jcr/107.98.7/README.md new file mode 100644 index 000000000..c0051e61d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/app-readme.md b/charts/jfrog/artifactory-jcr/107.98.7/app-readme.md new file mode 100644 index 000000000..9d9b7d85f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/.helmignore b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/.helmignore new file mode 100644 index 000000000..b6e97f07f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/CHANGELOG.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/CHANGELOG.md new file mode 100644 index 000000000..15f9b2f34 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/CHANGELOG.md @@ -0,0 +1,1388 @@ +# JFrog Artifactory Chart Changelog +All changes to this chart will be documented in this file. + +## [107.98.7] - Oct 02, 2024 +* Add support for `extraEnvironmentVariables` on filebeat Sidecar [GH-1377](https://github.com/jfrog/charts/pull/1377) +* Support for SSL offload HTTPS proto override in Nginx service (ClusterIP, LoadBalancer) layer. Introduced `nginx.service.ssloffloadForceHttps` field with boolean type. [GH-1906](https://github.com/jfrog/charts/pull/1906) +* Enable Access workers integration when artifactory.worker.enabled is true +* Added `signedUrlExpirySeconds` option to artifactory.persistence.type of `google-storage`, `google-storage-v2`, and `google-storage-v2-direct` [GH-1858](https://github.com/jfrog/charts/pull/1858) +* Added support to bootstrap jfconnect custom certs to jfconnect trusted directory + +## [107.96.0] - Sep 10, 2024 +* Merged Artifactory sizing templates to a single file per size + +## [107.94.0] - Aug 14, 2024 +* Fixed #Expose rtfs port only when it is enabled + +## [107.93.0] - Aug 9, 2024 +* Added support for worker via `artifactory.worker.enabled` flag +* Fixed creation of duplicate objects in statefulSet + +## [107.92.0] - July 31, 2024 +* Updating the example link for downloading the DB driver +* Adding dedicated ingress and service path for GRPC protocol + +## [107.91.0] - July 18, 2024 +* Remove X-JFrog-Override-Base-Url port when using default `443/80` ports + +## [107.90.0] - 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` +* 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.98.7/charts/artifactory/Chart.lock b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/Chart.lock new file mode 100644 index 000000000..8064c323b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/Chart.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/Chart.yaml new file mode 100644 index 000000000..7ba8a6f1e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: 7.98.7 +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.98.7 diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/LICENSE b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/README.md new file mode 100644 index 000000000..783101786 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/README.md @@ -0,0 +1,60 @@ +# 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 +Note that sizings with more than one replica require an enterprise license for HA . Refer [here](https://jfrog.com/help/r/jfrog-installation-setup-documentation/high-availability) +To apply the chart with recommended sizing configurations : +For small configurations : +```bash +helm upgrade --install artifactory jfrog/artifactory -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.98.7/charts/artifactory/charts/postgresql/.helmignore b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/Chart.lock b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/Chart.lock new file mode 100644 index 000000000..3687f52df --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/Chart.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/Chart.yaml new file mode 100644 index 000000000..4b197b207 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/README.md new file mode 100644 index 000000000..63d3605bb --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/.helmignore b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/Chart.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/Chart.yaml new file mode 100644 index 000000000..bcc3808d0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/README.md new file mode 100644 index 000000000..7287cbb5f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_affinities.tpl new file mode 100644 index 000000000..493a6dc7e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100644 index 000000000..4dde56a38 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100644 index 000000000..a79cc2e32 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_images.tpl new file mode 100644 index 000000000..60f04fd6e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_ingress.tpl new file mode 100644 index 000000000..622ef50e3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100644 index 000000000..252066c7e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_names.tpl new file mode 100644 index 000000000..adf2a74f4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100644 index 000000000..60b84a701 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100644 index 000000000..60e2a844f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 000000000..2db166851 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100644 index 000000000..ea083a249 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100644 index 000000000..ae10fa41e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 000000000..8679ddffb --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 000000000..bb5ed7253 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 000000000..7d5ecbccb --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 000000000..992bcd390 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 000000000..3e2a47c03 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 000000000..9a814cf40 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/charts/common/values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/charts/common/values.yaml new file mode 100644 index 000000000..9ecdc93f5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/commonAnnotations.yaml new file mode 100644 index 000000000..97e18a4cc --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/default-values.yaml new file mode 100644 index 000000000..fc2ba605a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml new file mode 100644 index 000000000..347d3b40a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/ci/shmvolume-disabled-values.yaml @@ -0,0 +1,2 @@ +shmVolume: + enabled: false diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/README.md new file mode 100644 index 000000000..1813a2fea --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/files/conf.d/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/conf.d/README.md new file mode 100644 index 000000000..184c1875d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/files/docker-entrypoint-initdb.d/README.md new file mode 100644 index 000000000..cba38091e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/NOTES.txt new file mode 100644 index 000000000..4e98958c1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/_helpers.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 000000000..1f98efe78 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/configmap.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/configmap.yaml new file mode 100644 index 000000000..3a5ea18ae --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/extended-config-configmap.yaml new file mode 100644 index 000000000..b0dad253b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/extra-list.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/extra-list.yaml new file mode 100644 index 000000000..9ac65f9e1 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/initialization-configmap.yaml new file mode 100644 index 000000000..7796c67a9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/metrics-configmap.yaml new file mode 100644 index 000000000..fa539582b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/metrics-svc.yaml new file mode 100644 index 000000000..af8b67e2f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/networkpolicy.yaml new file mode 100644 index 000000000..4f2740ea0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..0c49694fa --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/prometheusrule.yaml new file mode 100644 index 000000000..d0f408c78 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/role.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/role.yaml new file mode 100644 index 000000000..017a5716b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/rolebinding.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/rolebinding.yaml new file mode 100644 index 000000000..189775a15 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/secrets.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/secrets.yaml new file mode 100644 index 000000000..d492cd593 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/serviceaccount.yaml new file mode 100644 index 000000000..03f0f50e7 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/servicemonitor.yaml new file mode 100644 index 000000000..587ce85b8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/statefulset-readreplicas.yaml new file mode 100644 index 000000000..b038299bf --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/statefulset.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 000000000..f8163fd99 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/svc-headless.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc-headless.yaml new file mode 100644 index 000000000..6f5f3b9ee --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/svc-read.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc-read.yaml new file mode 100644 index 000000000..56195ea1e --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/templates/svc.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/templates/svc.yaml new file mode 100644 index 000000000..a29431b6a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/values.schema.json b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/values.schema.json new file mode 100644 index 000000000..66a2a9dd0 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/charts/postgresql/values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/charts/postgresql/values.yaml new file mode 100644 index 000000000..82ce09234 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/access-tls-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/access-tls-values.yaml new file mode 100644 index 000000000..1a8c4698d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/default-values.yaml new file mode 100644 index 000000000..fc3469399 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/derby-test-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/derby-test-values.yaml new file mode 100644 index 000000000..82ff48545 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/global-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/global-values.yaml new file mode 100644 index 000000000..33bbf04a2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/large-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/large-values.yaml new file mode 100644 index 000000000..94a485d6f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/loggers-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/loggers-values.yaml new file mode 100644 index 000000000..03c94be95 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/medium-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/medium-values.yaml new file mode 100644 index 000000000..35044dc36 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/migration-disabled-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/migration-disabled-values.yaml new file mode 100644 index 000000000..092756fb6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/nginx-autoreload-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/nginx-autoreload-values.yaml new file mode 100644 index 000000000..09616c5bf --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/rtsplit-values-access-tls-values.yaml new file mode 100644 index 000000000..a38969a8f --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/rtsplit-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/rtsplit-values.yaml new file mode 100644 index 000000000..057ae9bf3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/small-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/small-values.yaml new file mode 100644 index 000000000..70d77790a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/ci/test-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/ci/test-values.yaml new file mode 100644 index 000000000..d2beb0eff --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/files/binarystore.xml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/binarystore.xml new file mode 100644 index 000000000..b02ac8ad5 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/binarystore.xml @@ -0,0 +1,431 @@ +{{- 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 }} + {{- if .Values.artifactory.persistence.googleStorage.signedUrlExpirySeconds }} + true + {{- else }} + false + {{- end }} + + +{{- 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.98.7/charts/artifactory/files/installer-info.json b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/installer-info.json new file mode 100644 index 000000000..79f42ed16 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/files/migrate.sh b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/migrate.sh new file mode 100644 index 000000000..ba44160f4 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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}} + {{- if .Values.nginx.service.ssloffloadForceHttps}} + proxy_set_header X-JFrog-Override-Base-Url https://$host; + proxy_set_header X-Forwarded-Proto https; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- end }} + {{- else if or (eq (int .Values.nginx.https.internalPort) 80) (eq (int .Values.nginx.https.externalPort) 443)}} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- else }} + proxy_set_header X-JFrog-Override-Base-Url $http_x_forwarded_proto://$host:$host_port; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + {{- end }} + 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.98.7/charts/artifactory/files/nginx-main-conf.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/nginx-main-conf.yaml new file mode 100644 index 000000000..6ee7f98f9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/files/system.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/system.yaml new file mode 100644 index 000000000..fecc70714 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/files/system.yaml @@ -0,0 +1,167 @@ +router: + serviceRegistry: + insecure: {{ .Values.router.serviceRegistry.insecure }} +shared: +{{- if .Values.artifactory.coldStorage.enabled }} + jfrogColdStorage: + coldInstanceEnabled: true +{{- end }} +{{- if .Values.artifactory.worker.enabled }} + featureToggler: + worker: 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 }} +{{- if .Values.artifactory.worker.enabled }} + -Dartifactory.workers.addon.support=true +{{- end }} + {{- 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.artifactory.worker.enabled }} + worker: + enabled: true +{{- 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-jcr/107.98.7/charts/artifactory/logo/artifactory-logo.png b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/logo/artifactory-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6c23c5a7f87edaf49f883ffa99d875073e2285 GIT binary patch literal 82419 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 z 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 GZv9`RA2Y%L literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-2xlarge.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-2xlarge.yaml new file mode 100644 index 000000000..e3f9a3442 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-2xlarge.yaml @@ -0,0 +1,158 @@ +############################################################################################## +# The 2xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + + 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 + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + # cpu: 2 + memory: 4Gi + +router: + resources: + requests: + cpu: "2" + memory: 2Gi + limits: + # cpu: "12" + memory: 4Gi + +frontend: + resources: + requests: + cpu: "1" + memory: 500Mi + limits: + # cpu: "5" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 200 + 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" diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-large.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-large.yaml new file mode 100644 index 000000000..ec52b6077 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-large.yaml @@ -0,0 +1,158 @@ +############################################################################################## +# The large sizing +# This size is intended for large organizations. It can be increased with adding replicas or moving to the xlarge sizing +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "8" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 125 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 100 + resources: + requests: + cpu: 1 + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 400m + memory: 800Mi + limits: + # cpu: "8" + memory: 2Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 100 + 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.98.7/charts/artifactory/sizing/artifactory-medium.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-medium.yaml new file mode 100644 index 000000000..16045600d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-medium.yaml @@ -0,0 +1,158 @@ +############################################################################################## +# The medium sizing +# This size is just 2 replicas of the small size. Vertical sizing of all services is not changed +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "2" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + database: + maxOpenConnections: 50 + 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.98.7/charts/artifactory/sizing/artifactory-small.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-small.yaml new file mode 100644 index 000000000..9593936e9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-small.yaml @@ -0,0 +1,158 @@ +############################################################################################## +# The small sizing +# This is the size recommended for running Artifactory for small teams +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 75 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 50 + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 200m + memory: 500Mi + limits: + # cpu: "2" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 100m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + database: + maxOpenConnections: 50 + 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.98.7/charts/artifactory/sizing/artifactory-xlarge.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xlarge.yaml new file mode 100644 index 000000000..92f9f317b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xlarge.yaml @@ -0,0 +1,158 @@ +############################################################################################## +# The xlarge sizing +# This size is intended for very large organizations. It can be increased with adding replicas +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "16" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 150 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 150 + resources: + requests: + cpu: 500m + memory: 2Gi + limits: + # cpu: 1 + memory: 3Gi + +router: + resources: + requests: + cpu: 400m + memory: 1Gi + limits: + # cpu: "8" + memory: 2Gi + +frontend: + resources: + requests: + cpu: 200m + memory: 300Mi + limits: + # cpu: "3" + memory: 1Gi + +metadata: + database: + maxOpenConnections: 150 + 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.98.7/charts/artifactory/sizing/artifactory-xsmall.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xsmall.yaml new file mode 100644 index 000000000..cc61f12e8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/sizing/artifactory-xsmall.yaml @@ -0,0 +1,160 @@ +############################################################################################## +# The xsmall sizing +# This is the minimum size recommended for running Artifactory +# [WARNING] Some of the the configuration mentioned in this file are taken inside system.yaml +# hence this configuration will be overridden when enabling systemYamlOverride +############################################################################################## +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 + + 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 + + extraEnvironmentVariables: + - name: MALLOC_ARENA_MAX + value: "2" + - name: SKIP_WAIT_FOR_EXTERNAL_DB + value: "true" + +access: + tomcat: + connector: + maxThreads: 15 + javaOpts: + other: > + -XX:InitialRAMPercentage=20 + -XX:MaxRAMPercentage=60 + database: + maxOpenConnections: 15 + resources: + requests: + cpu: 500m + memory: 1.5Gi + limits: + # cpu: 1 + memory: 2Gi + +router: + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + # cpu: "2" + memory: 1Gi + +frontend: + resources: + requests: + cpu: 50m + memory: 150Mi + limits: + # cpu: "2" + memory: 250Mi + +metadata: + database: + maxOpenConnections: 15 + 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.98.7/charts/artifactory/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/NOTES.txt new file mode 100644 index 000000000..76652ac98 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/_helpers.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_helpers.tpl new file mode 100644 index 000000000..8e517e000 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_helpers.tpl @@ -0,0 +1,544 @@ +{{/* 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 -}} + +{{/* +Create a default fully qualified grpc ingress name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "artifactory.ingressGrpc.fullname" -}} +{{- printf "%s-%s" (include "artifactory.fullname" .) .Values.ingressGrpc.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified grpc service name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "artifactory.serviceGrpc.fullname" -}} +{{- printf "%s-%s" (include "artifactory.fullname" .) .Values.artifactory.serviceGrpc.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} \ No newline at end of file diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_system-yaml-render.tpl b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/_system-yaml-render.tpl new file mode 100644 index 000000000..deaa773ea --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/additional-resources.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/additional-resources.yaml new file mode 100644 index 000000000..c4d06f08a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/admin-bootstrap-creds.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/admin-bootstrap-creds.yaml new file mode 100644 index 000000000..eb2d613c6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-access-config.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-access-config.yaml new file mode 100644 index 000000000..4fcf85d94 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-binarystore-secret.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-binarystore-secret.yaml new file mode 100644 index 000000000..6b721dd4c --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-configmaps.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-configmaps.yaml new file mode 100644 index 000000000..359fa07d2 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-custom-secrets.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-custom-secrets.yaml new file mode 100644 index 000000000..4b73e79fc --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-database-secrets.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-database-secrets.yaml new file mode 100644 index 000000000..f98d422e9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-gcp-credentials-secret.yaml new file mode 100644 index 000000000..72dee6bb8 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-hpa.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-hpa.yaml new file mode 100644 index 000000000..01f8a9fb7 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-installer-info.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-installer-info.yaml new file mode 100644 index 000000000..cfb95b67d --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-license-secret.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-license-secret.yaml new file mode 100644 index 000000000..dda734033 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-license-secret.yaml @@ -0,0 +1,16 @@ +{{ if and (not .Values.artifactory.unifiedSecretInstallation) (not .Values.artifactory.license.secret) }} +{{- 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.98.7/charts/artifactory/templates/artifactory-migration-scripts.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-migration-scripts.yaml new file mode 100644 index 000000000..4b1ba4027 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-networkpolicy.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-networkpolicy.yaml new file mode 100644 index 000000000..d24203dc9 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-nfs-pvc.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-nfs-pvc.yaml new file mode 100644 index 000000000..75d6d0c53 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/artifactory-pdb.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/artifactory-pdb.yaml new file mode 100644 index 000000000..68876d23b --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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 .Values.jfconnect.customCertificatesSecretName }} + echo "Copying custom certificates to {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys"; + mkdir -p {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys; + cp -fv /tmp/etc/tls.crt {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.cer {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.pem {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + cp -fv /tmp/etc/tls.key {{ .Values.artifactory.persistence.mountPath }}/bootstrap/jfconnect/etc/keys/; + {{- 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 .Values.jfconnect.customCertificatesSecretName }} + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.crt" + subPath: tls.crt + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.pem" + subPath: tls.pem + - name: jfconnect-certs + mountPath: "/tmp/etc/tls.cer" + subPath: tls.cer + - name: jfconnect-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 }} + 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 }} + {{- if .Values.federation.enabled }} + ports: + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + 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 + {{- if .Values.federation.enabled }} + - containerPort: {{ .Values.federation.internalPort }} + name: http-rtfs + {{- end }} + {{- 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.ingressGrpc.className }} + {{- end }} + {{- if .Values.ingressGrpc.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.ingressGrpc.hosts }} + {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} + {{- range $host := .Values.ingressGrpc.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingressGrpc.grpcPath }} + pathType: ImplementationSpecific + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end }} + {{- else }} + {{- range $host := .Values.ingressGrpc.hosts }} + - host: {{ $host | quote }} + http: + paths: + - path: {{ $.Values.ingressGrpc.grpcPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} + {{- end }} +{{- end -}} + {{- with .Values.ingressGrpc.additionalRules }} +{{ tpl . $ | indent 2 }} + {{- end }} + + {{- if .Values.ingressGrpc.tls }} + tls: +{{ toYaml .Values.ingressGrpc.tls | indent 4 }} + {{- end -}} + +{{- end -}} diff --git a/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/ingress.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/ingress.yaml new file mode 100644 index 000000000..fca895e47 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/ingress.yaml @@ -0,0 +1,109 @@ +{{- if .Values.ingress.enabled -}} +{{- $serviceName := include "artifactory.fullname" . -}} +{{- $servicePort := .Values.artifactory.externalPort -}} +{{- $artifactoryServicePort := .Values.artifactory.externalArtifactoryPort -}} +{{- $ingressName := default ( include "artifactory.fullname" . ) .Values.ingress.name -}} +{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} +apiVersion: networking.k8s.io/v1 +{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $ingressName }} + labels: + app: {{ template "artifactory.name" . }} + chart: {{ template "artifactory.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.ingress.labels }} +{{ .Values.ingress.labels | toYaml | trimSuffix "\n"| indent 4 -}} +{{- end}} +{{- if .Values.ingress.annotations }} + annotations: +{{ .Values.ingress.annotations | toYaml | trimSuffix "\n" | indent 4 -}} +{{- end }} +spec: + {{- if and .Values.ingress.className (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.98.7/charts/artifactory/templates/logger-configmap.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/logger-configmap.yaml new file mode 100644 index 000000000..41a078b02 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/nginx-artifactory-conf.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-artifactory-conf.yaml new file mode 100644 index 000000000..343448994 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/nginx-certificate-secret.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-certificate-secret.yaml new file mode 100644 index 000000000..f13d40174 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/nginx-conf.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-conf.yaml new file mode 100644 index 000000000..31219d58a --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/nginx-deployment.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-deployment.yaml new file mode 100644 index 000000000..774bedcca --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/charts/artifactory/templates/nginx-pdb.yaml b/charts/jfrog/artifactory-jcr/107.98.7/charts/artifactory/templates/nginx-pdb.yaml new file mode 100644 index 000000000..dff0c23a3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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: "" +## There is a need to separate HTTP1.1 traffic from gRPC (HTTP2) to benefit +## 1. Use the Nginx keepalive for HTTP1.1 +## 2. Keep gRPC over the HTTP2 connections only +ingressGrpc: + enabled: false + name: grpc + defaultBackend: + enabled: true + ## Used to create an Ingress record. + hosts: [] + grpcPath: /com.jfrog + className: "" + ## For supporting GRPC protocol traffic sent to the backend it is necessary to change the backend protocol + ## Example: nginx.ingress.kubernetes.io/backend-protocol: GRPCS + annotations: {} + # nginx.ingress.kubernetes.io/configuration-snippet: | + # proxy_pass_header Server; + # proxy_set_header X-JFrog-Override-Base-Url https://; + # 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: [] +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.135.1 + 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 + ## Support for workers + ## set 'worker.enabled' to 'true' to enable artifactory addon that executes workers on artifactory events + worker: + 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 + serviceGrpc: + name: grpc + 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 + # signedUrlExpirySeconds: 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 " --cert=tls.crt --key=tls.key` + # customCertificatesSecretName: + + ## 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.jfconnect.internalPort }}/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.ssloffloadForceHttps Only enabled when service.ssloffload is set to True. + ## Force all requests from NGINX to the upstream server are over HTTPS, even when SSL offloading is enabled. + ## This is useful in environments where internal traffic must remain secure with https only. + ssloffloadForceHttps: 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 }}"] + extraEnvironmentVariables: {} + # - name: MY_ENV_VAR + # value: "s" +## 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.98.7/ci/default-values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/ci/default-values.yaml new file mode 100644 index 000000000..86355d3b3 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/logo/jcr-logo.png b/charts/jfrog/artifactory-jcr/107.98.7/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 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 f`MkUvo-M@p#hFOE>-xWCTUi literal 0 HcmV?d00001 diff --git a/charts/jfrog/artifactory-jcr/107.98.7/questions.yml b/charts/jfrog/artifactory-jcr/107.98.7/questions.yml new file mode 100644 index 000000000..9cde42870 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.98.7/templates/NOTES.txt b/charts/jfrog/artifactory-jcr/107.98.7/templates/NOTES.txt new file mode 100644 index 000000000..035bf8417 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/templates/NOTES.txt @@ -0,0 +1 @@ +Congratulations. You have just deployed JFrog Container Registry! diff --git a/charts/jfrog/artifactory-jcr/107.98.7/values.yaml b/charts/jfrog/artifactory-jcr/107.98.7/values.yaml new file mode 100644 index 000000000..d1e4702b6 --- /dev/null +++ b/charts/jfrog/artifactory-jcr/107.98.7/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.135.1 +initContainers: + image: + tag: 9.4.1227 diff --git a/charts/new-relic/nri-bundle/5.0.98/.helmignore b/charts/new-relic/nri-bundle/5.0.98/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/.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/new-relic/nri-bundle/5.0.98/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/Chart.lock new file mode 100644 index 000000000..1212c7529 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/Chart.lock @@ -0,0 +1,39 @@ +dependencies: +- name: newrelic-infrastructure + repository: https://newrelic.github.io/nri-kubernetes + version: 3.35.0 +- name: nri-prometheus + repository: https://newrelic.github.io/nri-prometheus + version: 2.1.19 +- name: newrelic-prometheus-agent + repository: https://newrelic.github.io/newrelic-prometheus-configurator + version: 1.15.0 +- name: nri-metadata-injection + repository: https://newrelic.github.io/k8s-metadata-injection + version: 4.22.0 +- name: newrelic-k8s-metrics-adapter + repository: https://newrelic.github.io/newrelic-k8s-metrics-adapter + version: 1.12.0 +- name: kube-state-metrics + repository: https://prometheus-community.github.io/helm-charts + version: 5.26.0 +- name: nri-kube-events + repository: https://newrelic.github.io/nri-kube-events + version: 3.11.0 +- name: newrelic-logging + repository: https://newrelic.github.io/helm-charts + version: 1.23.1 +- name: newrelic-pixie + repository: https://newrelic.github.io/helm-charts + version: 2.1.6 +- name: k8s-agents-operator + repository: https://newrelic.github.io/k8s-agents-operator + version: 0.16.1 +- name: pixie-operator-chart + repository: https://pixie-operator-charts.storage.googleapis.com + version: 0.1.6 +- name: newrelic-infra-operator + repository: https://newrelic.github.io/newrelic-infra-operator + version: 2.12.0 +digest: sha256:2530cda1be1402806b1ea3aabe4a1839765f9b0b5dd850f03bb7d96b1eaa9b6e +generated: "2024-10-28T17:46:44.820558036Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/Chart.yaml new file mode 100644 index 000000000..cd1dd0472 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/Chart.yaml @@ -0,0 +1,85 @@ +annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: New Relic + catalog.cattle.io/release-name: nri-bundle +apiVersion: v2 +dependencies: +- condition: infrastructure.enabled,newrelic-infrastructure.enabled + name: newrelic-infrastructure + repository: https://newrelic.github.io/nri-kubernetes + version: 3.35.0 +- condition: prometheus.enabled,nri-prometheus.enabled + name: nri-prometheus + repository: https://newrelic.github.io/nri-prometheus + version: 2.1.19 +- condition: newrelic-prometheus-agent.enabled + name: newrelic-prometheus-agent + repository: https://newrelic.github.io/newrelic-prometheus-configurator + version: 1.15.0 +- condition: webhook.enabled,nri-metadata-injection.enabled + name: nri-metadata-injection + repository: https://newrelic.github.io/k8s-metadata-injection + version: 4.22.0 +- condition: metrics-adapter.enabled,newrelic-k8s-metrics-adapter.enabled + name: newrelic-k8s-metrics-adapter + repository: https://newrelic.github.io/newrelic-k8s-metrics-adapter + version: 1.12.0 +- condition: ksm.enabled,kube-state-metrics.enabled + name: kube-state-metrics + repository: https://prometheus-community.github.io/helm-charts + version: 5.26.0 +- condition: kubeEvents.enabled,nri-kube-events.enabled + name: nri-kube-events + repository: https://newrelic.github.io/nri-kube-events + version: 3.11.0 +- condition: logging.enabled,newrelic-logging.enabled + name: newrelic-logging + repository: https://newrelic.github.io/helm-charts + version: 1.23.1 +- condition: newrelic-pixie.enabled + name: newrelic-pixie + repository: https://newrelic.github.io/helm-charts + version: 2.1.6 +- condition: k8s-agents-operator.enabled + name: k8s-agents-operator + repository: https://newrelic.github.io/k8s-agents-operator + version: 0.16.1 +- alias: pixie-chart + condition: pixie-chart.enabled + name: pixie-operator-chart + repository: https://pixie-operator-charts.storage.googleapis.com + version: 0.1.6 +- condition: newrelic-infra-operator.enabled + name: newrelic-infra-operator + repository: https://newrelic.github.io/newrelic-infra-operator + version: 2.12.0 +description: Groups together the individual charts for the New Relic Kubernetes solution + for a more comfortable deployment. +home: https://github.com/newrelic/helm-charts +icon: file://assets/icons/nri-bundle.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: nri-bundle +sources: +- https://github.com/newrelic/nri-bundle/ +- https://github.com/newrelic/nri-bundle/tree/master/charts/nri-bundle +- https://github.com/newrelic/nri-kubernetes/tree/master/charts/newrelic-infrastructure +- https://github.com/newrelic/nri-prometheus/tree/master/charts/nri-prometheus +- https://github.com/newrelic/newrelic-prometheus-configurator/tree/master/charts/newrelic-prometheus-agent +- https://github.com/newrelic/k8s-metadata-injection/tree/master/charts/nri-metadata-injection +- https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/master/charts/newrelic-k8s-metrics-adapter +- https://github.com/newrelic/nri-kube-events/tree/master/charts/nri-kube-events +- https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging +- https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie +- https://github.com/newrelic/newrelic-infra-operator/tree/master/charts/newrelic-infra-operator +- https://github.com/newrelic/k8s-agents-operator/tree/master/charts/k8s-agents-operator +version: 5.0.98 diff --git a/charts/new-relic/nri-bundle/5.0.98/README.md b/charts/new-relic/nri-bundle/5.0.98/README.md new file mode 100644 index 000000000..3fcc97d2b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/README.md @@ -0,0 +1,200 @@ +# nri-bundle + +Groups together the individual charts for the New Relic Kubernetes solution for a more comfortable deployment. + +**Homepage:** + +## Bundled charts + +This chart does not deploy anything by itself but has many charts as dependencies. This allows you to easily install and upgrade the New Relic +Kubernetes Integration using only one chart. + +In case you need more information about each component this chart installs, or you are an advanced user that want to install each component separately, +here is a list of components that this chart installs and where you can find more information about them: + +| Component | Installed by default? | Description | +|------------------------------|-----------------------|-------------| +| [newrelic-infrastructure](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) | Yes | Sends metrics about nodes, cluster objects (e.g. Deployments, Pods), and the control plane to New Relic. | +| [nri-metadata-injection](https://github.com/newrelic/k8s-metadata-injection/tree/main/charts/nri-metadata-injection) | Yes | Enriches New Relic-instrumented applications (APM) with Kubernetes information. | +| [kube-state-metrics](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-state-metrics) | | Required for `newrelic-infrastructure` to gather cluster-level metrics. | +| [nri-kube-events](https://github.com/newrelic/nri-kube-events/tree/main/charts/nri-kube-events) | | Reports Kubernetes events to New Relic. | +| [newrelic-infra-operator](https://github.com/newrelic/newrelic-infra-operator/tree/main/charts/newrelic-infra-operator) | | (Beta) Used with Fargate or serverless environments to inject `newrelic-infrastructure` as a sidecar instead of the usual DaemonSet. | +| [newrelic-k8s-metrics-adapter](https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/main/charts/newrelic-k8s-metrics-adapter) | | (Beta) Provides a source of data for Horizontal Pod Autoscalers (HPA) based on a NRQL query from New Relic. | +| [newrelic-logging](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging) | | Sends logs for Kubernetes components and workloads running on the cluster to New Relic. | +| [nri-prometheus](https://github.com/newrelic/nri-prometheus/tree/main/charts/nri-prometheus) | | Sends metrics from applications exposing Prometheus metrics to New Relic. | +| [newrelic-prometheus-configurator](https://github.com/newrelic/newrelic-prometheus-configurator/tree/master/charts/newrelic-prometheus-agent) | | Configures instances of Prometheus in Agent mode to send metrics to the New Relic Prometheus endpoint. | +| [newrelic-pixie](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie) | | Connects to the Pixie API and enables the New Relic plugin in Pixie. The plugin allows you to export data from Pixie to New Relic for long-term data retention. | +| [Pixie](https://docs.pixielabs.ai/installing-pixie/install-schemes/helm/#3.-deploy) | | Is an open source observability tool for Kubernetes applications that uses eBPF to automatically capture telemetry data without the need for manual instrumentation. | +| [k8s-agents-operator](https://github.com/newrelic/k8s-agents-operator/tree/main/charts/k8s-agents-operator) | | (Preview) Streamlines full-stack observability for Kubernetes environments by automating APM instrumentation alongside Kubernetes agent deployment. | + +## Configure components + +It is possible to configure settings for the individual charts this chart groups by specifying values for them under a key using the name of the chart, +as specified in [helm documentation](https://helm.sh/docs/chart_template_guide/subcharts_and_globals). + +For example, by adding the following to the `values.yml` file: + +```yaml +# Configuration settings for the newrelic-infrastructure chart +newrelic-infrastructure: + # Any key defined in the values.yml file for the newrelic-infrastructure chart can be configured here: + # https://github.com/newrelic/nri-kubernetes/blob/main/charts/newrelic-infrastructure/values.yaml + + verboseLog: false + + resources: + limits: + memory: 512M +``` + +It is possible to override any entry of the [`newrelic-infrastructure`](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) +chart, as defined in their [`values.yml` file](https://github.com/newrelic/nri-kubernetes/blob/main/charts/newrelic-infrastructure/values.yaml). + +The same approach can be followed to update any of the subcharts. + +After making these changes to the `values.yml` file, or a custom values file, make sure to apply them using: + +``` +$ helm upgrade --reuse-values -f values.yaml [RELEASE] newrelic/nri-bundle +``` + +Where `[RELEASE]` is the name of the helm release, e.g. `newrelic-bundle`. + +## Monitor on host integrations + +If you wish to monitor services running on Kubernetes you can provide integrations +configuration under `integrations_config` that it will passed down to the `newrelic-infrastructure` chart. + +You just need to create a new entry where the "name" is the filename of the configuration file and the data is the content of +the integration configuration. The name must end in ".yaml" as this will be the +filename generated and the Infrastructure agent only looks for YAML files. + +The data part is the actual integration configuration as described in the spec here: +https://docs.newrelic.com/docs/integrations/integrations-sdk/file-specifications/integration-configuration-file-specifications-agent-v180 + +In the following example you can see how to monitor a Redis integration with autodiscovery + +```yaml +newrelic-infrastructure: + integrations: + nri-redis-sampleapp: + discovery: + command: + exec: /var/db/newrelic-infra/nri-discovery-kubernetes --tls --port 10250 + match: + label.app: sampleapp + integrations: + - name: nri-redis + env: + # using the discovered IP as the hostname address + HOSTNAME: ${discovery.ip} + PORT: 6379 + labels: + env: test +``` + +## Bring your own KSM + +New Relic Kubernetes Integration requires an instance of kube-state-metrics (KSM) to be running in the cluster, which this chart pulls as a dependency. If you are already running or want to run your own KSM instance, you will need to make some small adjustments as described below. + +### Bring your own KSM + +If you already have one KSM instance running, you can point `nri-kubernetes` to your instance: + +```yaml +kube-state-metrics: + # Disable bundled KSM. + enabled: false +newrelic-infrastructure: + ksm: + config: + # Selector for your pre-installed KSM Service. You may need to adjust this to fit your existing installation. + selector: "app.kubernetes.io/name=kube-state-metrics" + # Alternatively, you can specify a fixed URL where KSM is available. Doing so will bypass autodiscovery. + #staticUrl: http://ksm.ksm.svc.cluster.local:8080/metrics +``` + +### Run KSM alongside a different version + +If you need to run a different instance of KSM in your cluster, you can still run a separate instance for the Kubernetes Integration to work as intended: + +```yaml +kube-state-metrics: + # Enable bundled KSM. + enabled: true + prometheusScrape: false + customLabels: + # Label unique to this KSM instance. + newrelic.com/custom-ksm: "true" +newrelic-infrastructure: + ksm: + config: + # Use label above as a selector. + selector: "newrelic.com/custom-ksm=true" +``` + +For more information on supported KSM version visit the [requirements documentation](https://docs.newrelic.com/docs/kubernetes-pixie/kubernetes-integration/get-started/kubernetes-integration-compatibility-requirements#reqs) + +## Values managed globally + +Some of the subchart implement the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +At the time of writing this document, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this library and +honors global options as described below. + +Note, the value table below is automatically generated from `values.yaml` by `helm-docs`. If you need to add new fields or update existing fields, please update the `values.yaml` and then run `helm-docs` to update this value table. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global | object | See [`values.yaml`](values.yaml) | change the behaviour globally to all the supported helm charts. See [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) for further information. | +| global.affinity | object | `{}` | Sets pod/node affinities | +| global.cluster | string | `""` | The cluster name for the Kubernetes cluster. | +| global.containerSecurityContext | object | `{}` | Sets security context (at container level) | +| global.customAttributes | object | `{}` | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.customSecretLicenseKey | string | `""` | Key in the Secret object where the license key is stored | +| global.customSecretName | string | `""` | Name of the Secret object where the license key is stored | +| global.dnsConfig | object | `{}` | Sets pod's dnsConfig | +| global.fargate | bool | false | Must be set to `true` when deploying in an EKS Fargate environment | +| global.hostNetwork | bool | false | Sets pod's hostNetwork | +| global.images.pullSecrets | list | `[]` | Set secrets to be able to fetch images | +| global.images.registry | string | `""` | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.insightsKey | string | `""` | The license key for your New Relic Account. This will be preferred configuration option if both `insightsKey` and `customSecret` are specified. | +| global.labels | object | `{}` | Additional labels for chart objects | +| global.licenseKey | string | `""` | The license key for your New Relic Account. This will be preferred configuration option if both `licenseKey` and `customSecret` are specified. | +| global.lowDataMode | bool | false | Reduces number of metrics sent in order to reduce costs | +| global.nodeSelector | object | `{}` | Sets pod's node selector | +| global.nrStaging | bool | false | Send the metrics to the staging backend. Requires a valid staging license key | +| global.podLabels | object | `{}` | Additional labels for chart pods | +| global.podSecurityContext | object | `{}` | Sets security context (at pod level) | +| global.priorityClassName | string | `""` | Sets pod's priorityClassName | +| global.privileged | bool | false | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | | +| global.proxy | string | `""` | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.serviceAccount.annotations | object | `{}` | Add these annotations to the service account we create | +| global.serviceAccount.create | string | `nil` | Configures if the service account should be created or not | +| global.serviceAccount.name | string | `nil` | Change the name of the service account. This is honored if you disable on this chart the creation of the service account so you can use your own | +| global.tolerations | list | `[]` | Sets pod's tolerations to node taints | +| global.verboseLog | bool | false | Sets the debug logs to this integration or all integrations if it is set globally | +| k8s-agents-operator.enabled | bool | `false` | Install the [`k8s-agents-operator` chart](https://github.com/newrelic/k8s-agents-operator/tree/main/charts/k8s-agents-operator) | +| kube-state-metrics.enabled | bool | `false` | Install the [`kube-state-metrics` chart](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-state-metrics) from the stable helm charts repository. This is mandatory if `infrastructure.enabled` is set to `true` and the user does not provide its own instance of KSM version >=1.8 and <=2.0. Note, kube-state-metrics v2+ disables labels/annotations metrics by default. You can enable the target labels/annotations metrics to be monitored by using the metricLabelsAllowlist/metricAnnotationsAllowList options described [here](https://github.com/prometheus-community/helm-charts/blob/159cd8e4fb89b8b107dcc100287504bb91bf30e0/charts/kube-state-metrics/values.yaml#L274) in your Kubernetes clusters. | +| newrelic-infra-operator.enabled | bool | `false` | Install the [`newrelic-infra-operator` chart](https://github.com/newrelic/newrelic-infra-operator/tree/main/charts/newrelic-infra-operator) (Beta) | +| newrelic-infrastructure.enabled | bool | `true` | Install the [`newrelic-infrastructure` chart](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) | +| newrelic-k8s-metrics-adapter.enabled | bool | `false` | Install the [`newrelic-k8s-metrics-adapter.` chart](https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/main/charts/newrelic-k8s-metrics-adapter) (Beta) | +| newrelic-logging.enabled | bool | `false` | Install the [`newrelic-logging` chart](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging) | +| newrelic-pixie.enabled | bool | `false` | Install the [`newrelic-pixie`](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie) | +| newrelic-prometheus-agent.enabled | bool | `false` | Install the [`newrelic-prometheus-agent` chart](https://github.com/newrelic/newrelic-prometheus-configurator/tree/main/charts/newrelic-prometheus-agent) | +| nri-kube-events.enabled | bool | `false` | Install the [`nri-kube-events` chart](https://github.com/newrelic/nri-kube-events/tree/main/charts/nri-kube-events) | +| nri-metadata-injection.enabled | bool | `true` | Install the [`nri-metadata-injection` chart](https://github.com/newrelic/k8s-metadata-injection/tree/main/charts/nri-metadata-injection) | +| nri-prometheus.enabled | bool | `false` | Install the [`nri-prometheus` chart](https://github.com/newrelic/nri-prometheus/tree/main/charts/nri-prometheus) | +| pixie-chart.enabled | bool | `false` | Install the [`pixie-chart` chart](https://docs.pixielabs.ai/installing-pixie/install-schemes/helm/#3.-deploy) | + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) diff --git a/charts/new-relic/nri-bundle/5.0.98/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/README.md.gotmpl new file mode 100644 index 000000000..269c4925a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/README.md.gotmpl @@ -0,0 +1,166 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +## Bundled charts + +This chart does not deploy anything by itself but has many charts as dependencies. This allows you to easily install and upgrade the New Relic +Kubernetes Integration using only one chart. + +In case you need more information about each component this chart installs, or you are an advanced user that want to install each component separately, +here is a list of components that this chart installs and where you can find more information about them: + +| Component | Installed by default? | Description | +|------------------------------|-----------------------|-------------| +| [newrelic-infrastructure](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) | Yes | Sends metrics about nodes, cluster objects (e.g. Deployments, Pods), and the control plane to New Relic. | +| [nri-metadata-injection](https://github.com/newrelic/k8s-metadata-injection/tree/main/charts/nri-metadata-injection) | Yes | Enriches New Relic-instrumented applications (APM) with Kubernetes information. | +| [kube-state-metrics](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-state-metrics) | | Required for `newrelic-infrastructure` to gather cluster-level metrics. | +| [nri-kube-events](https://github.com/newrelic/nri-kube-events/tree/main/charts/nri-kube-events) | | Reports Kubernetes events to New Relic. | +| [newrelic-infra-operator](https://github.com/newrelic/newrelic-infra-operator/tree/main/charts/newrelic-infra-operator) | | (Beta) Used with Fargate or serverless environments to inject `newrelic-infrastructure` as a sidecar instead of the usual DaemonSet. | +| [newrelic-k8s-metrics-adapter](https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/main/charts/newrelic-k8s-metrics-adapter) | | (Beta) Provides a source of data for Horizontal Pod Autoscalers (HPA) based on a NRQL query from New Relic. | +| [newrelic-logging](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging) | | Sends logs for Kubernetes components and workloads running on the cluster to New Relic. | +| [nri-prometheus](https://github.com/newrelic/nri-prometheus/tree/main/charts/nri-prometheus) | | Sends metrics from applications exposing Prometheus metrics to New Relic. | +| [newrelic-prometheus-configurator](https://github.com/newrelic/newrelic-prometheus-configurator/tree/master/charts/newrelic-prometheus-agent) | | Configures instances of Prometheus in Agent mode to send metrics to the New Relic Prometheus endpoint. | +| [newrelic-pixie](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie) | | Connects to the Pixie API and enables the New Relic plugin in Pixie. The plugin allows you to export data from Pixie to New Relic for long-term data retention. | +| [Pixie](https://docs.pixielabs.ai/installing-pixie/install-schemes/helm/#3.-deploy) | | Is an open source observability tool for Kubernetes applications that uses eBPF to automatically capture telemetry data without the need for manual instrumentation. | +| [k8s-agents-operator](https://github.com/newrelic/k8s-agents-operator/tree/main/charts/k8s-agents-operator) | | (Preview) Streamlines full-stack observability for Kubernetes environments by automating APM instrumentation alongside Kubernetes agent deployment. | + +## Configure components + +It is possible to configure settings for the individual charts this chart groups by specifying values for them under a key using the name of the chart, +as specified in [helm documentation](https://helm.sh/docs/chart_template_guide/subcharts_and_globals). + +For example, by adding the following to the `values.yml` file: + +```yaml +# Configuration settings for the newrelic-infrastructure chart +newrelic-infrastructure: + # Any key defined in the values.yml file for the newrelic-infrastructure chart can be configured here: + # https://github.com/newrelic/nri-kubernetes/blob/main/charts/newrelic-infrastructure/values.yaml + + verboseLog: false + + resources: + limits: + memory: 512M +``` + +It is possible to override any entry of the [`newrelic-infrastructure`](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) +chart, as defined in their [`values.yml` file](https://github.com/newrelic/nri-kubernetes/blob/main/charts/newrelic-infrastructure/values.yaml). + +The same approach can be followed to update any of the subcharts. + +After making these changes to the `values.yml` file, or a custom values file, make sure to apply them using: + +``` +$ helm upgrade --reuse-values -f values.yaml [RELEASE] newrelic/nri-bundle +``` + +Where `[RELEASE]` is the name of the helm release, e.g. `newrelic-bundle`. + + +## Monitor on host integrations + +If you wish to monitor services running on Kubernetes you can provide integrations +configuration under `integrations_config` that it will passed down to the `newrelic-infrastructure` chart. + +You just need to create a new entry where the "name" is the filename of the configuration file and the data is the content of +the integration configuration. The name must end in ".yaml" as this will be the +filename generated and the Infrastructure agent only looks for YAML files. + +The data part is the actual integration configuration as described in the spec here: +https://docs.newrelic.com/docs/integrations/integrations-sdk/file-specifications/integration-configuration-file-specifications-agent-v180 + +In the following example you can see how to monitor a Redis integration with autodiscovery + +```yaml +newrelic-infrastructure: + integrations: + nri-redis-sampleapp: + discovery: + command: + exec: /var/db/newrelic-infra/nri-discovery-kubernetes --tls --port 10250 + match: + label.app: sampleapp + integrations: + - name: nri-redis + env: + # using the discovered IP as the hostname address + HOSTNAME: ${discovery.ip} + PORT: 6379 + labels: + env: test +``` + +## Bring your own KSM + +New Relic Kubernetes Integration requires an instance of kube-state-metrics (KSM) to be running in the cluster, which this chart pulls as a dependency. If you are already running or want to run your own KSM instance, you will need to make some small adjustments as described below. + +### Bring your own KSM + +If you already have one KSM instance running, you can point `nri-kubernetes` to your instance: + +```yaml +kube-state-metrics: + # Disable bundled KSM. + enabled: false +newrelic-infrastructure: + ksm: + config: + # Selector for your pre-installed KSM Service. You may need to adjust this to fit your existing installation. + selector: "app.kubernetes.io/name=kube-state-metrics" + # Alternatively, you can specify a fixed URL where KSM is available. Doing so will bypass autodiscovery. + #staticUrl: http://ksm.ksm.svc.cluster.local:8080/metrics +``` + +### Run KSM alongside a different version + +If you need to run a different instance of KSM in your cluster, you can still run a separate instance for the Kubernetes Integration to work as intended: + +```yaml +kube-state-metrics: + # Enable bundled KSM. + enabled: true + prometheusScrape: false + customLabels: + # Label unique to this KSM instance. + newrelic.com/custom-ksm: "true" +newrelic-infrastructure: + ksm: + config: + # Use label above as a selector. + selector: "newrelic.com/custom-ksm=true" +``` + +For more information on supported KSM version visit the [requirements documentation](https://docs.newrelic.com/docs/kubernetes-pixie/kubernetes-integration/get-started/kubernetes-integration-compatibility-requirements#reqs) + +## Values managed globally + +Some of the subchart implement the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +At the time of writing this document, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this library and +honors global options as described below. + +Note, the value table below is automatically generated from `values.yaml` by `helm-docs`. If you need to add new fields or update existing fields, please update the `values.yaml` and then run `helm-docs` to update this value table. + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/app-readme.md b/charts/new-relic/nri-bundle/5.0.98/app-readme.md new file mode 100644 index 000000000..61e550787 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/app-readme.md @@ -0,0 +1,5 @@ +# New Relic Kubernetes Integration + +New Relic's Kubernetes integration gives you full observability into the health and performance of your environment, no matter whether you run Kubernetes on-premises or in the cloud. With our [cluster explorer](https://docs.newrelic.com/docs/integrations/kubernetes-integration/cluster-explorer/kubernetes-cluster-explorer), you can cut through layers of complexity to see how your cluster is performing, from the heights of the control plane down to applications running on a single pod. + +You can see the power of the Kubernetes integration in the [cluster explorer](https://docs.newrelic.com/docs/integrations/kubernetes-integration/cluster-explorer/kubernetes-cluster-explorer), where the full picture of a cluster is made available on a single screen: nodes and pods are visualized according to their health and performance, with pending and alerting nodes in the innermost circles. [Predefined alert conditions](https://docs.newrelic.com/docs/integrations/kubernetes-integration/kubernetes-events/kubernetes-integration-predefined-alert-policy) help you troubleshoot issues right from the start. Clicking each node reveals its status and how each app is performing. \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/.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/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.lock new file mode 100644 index 000000000..1f868abfc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-10-25T18:35:38.878351812Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.yaml new file mode 100644 index 000000000..25902e46a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +appVersion: 0.16.1 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart for the Kubernetes Agents Operator +home: https://github.com/newrelic/k8s-agents-operator/blob/main/charts/k8s-agents-operator/README.md +maintainers: +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: danielstokes + url: https://github.com/danielstokes +name: k8s-agents-operator +sources: +- https://github.com/newrelic/k8s-agents-operator +type: application +version: 0.16.1 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md new file mode 100644 index 000000000..d274095bc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md @@ -0,0 +1,278 @@ +# k8s-agents-operator + +![Version: 0.16.1](https://img.shields.io/badge/Version-0.16.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.16.1](https://img.shields.io/badge/AppVersion-0.16.1-informational?style=flat-square) + +A Helm chart for the Kubernetes Agents Operator + +**Homepage:** + +## Prerequisites + +[Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm's [documentation](https://helm.sh/docs) to get started. + +## Installation + +### Requirements + +Add the `k8s-agents-operator` Helm chart repository: +```shell +helm repo add k8s-agents-operator https://newrelic.github.io/k8s-agents-operator +``` + +### Instrumentation + +Install the [`k8s-agents-operator`](https://github.com/newrelic/k8s-agents-operator) Helm chart: +```shell +helm upgrade --install k8s-agents-operator k8s-agents-operator/k8s-agents-operator \ + --namespace newrelic \ + --create-namespace \ + --values your-custom-values.yaml +``` + +### Monitored namespaces + +For each namespace you want the operator to be instrumented, a secret will be replicated from the newrelic operator namespace. + +For each `Instrumentation` custom resource created, specifying which APM agent you want to instrument for each language. All available APM + agent docker images and corresponding tags are listed on DockerHub: + +* [.NET](https://hub.docker.com/repository/docker/newrelic/newrelic-dotnet-init/general) +* [Java](https://hub.docker.com/repository/docker/newrelic/newrelic-java-init/general) +* [Node](https://hub.docker.com/repository/docker/newrelic/newrelic-node-init/general) +* [Python](https://hub.docker.com/repository/docker/newrelic/newrelic-python-init/general) +* [Ruby](https://hub.docker.com/repository/docker/newrelic/newrelic-ruby-init/general) + +For .NET + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-dotnet +spec: + agent: + language: dotnet + image: newrelic/newrelic-dotnet-init:latest + # env: ... +``` + +For Java + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-java + namespace: newrelic +spec: + agent: + language: java + image: newrelic/newrelic-java-init:latest + # env: ... +``` + +For NodeJS + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-nodejs + namespace: newrelic +spec: + agent: + language: nodejs + image: newrelic/newrelic-node-init:latest + # env: ... +``` + +For Python + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-python + namespace: newrelic +spec: + agent: + language: python + image: newrelic/newrelic-python-init:latest + # env: ... +``` + +For Ruby + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-ruby + namespace: newrelic +spec: + agent: + language: ruby + image: newrelic/newrelic-ruby-init:latest + # env: ... +``` + +For environment specific configurations + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + agent: + env: + # Example New Relic agent supported environment variables + - name: NEW_RELIC_LABELS + value: "environment:auto-injection" + # Example setting the pod name based on the metadata + - name: NEW_RELIC_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # Example overriding the appName configuration + - name: NEW_RELIC_APP_NAME + value: "$(NEW_RELIC_LABELS)-$(NEW_RELIC_POD_NAME)" +``` + +Targeting everything in a specific namespace with a label + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + #agent: ... + namespaceLabelSelector: + matchExpressions: + - key: "app.newrelic.instrumentation" + operator: "In" + values: ["java"] +``` + +Targeting a pod with a specific label + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + # agent: ... + podLabelSelector: + matchExpressions: + - key: "app.newrelic.instrumentation" + operator: "In" + values: ["dotnet"] +``` + +Using a secret with a non-default name + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + # agent: ... + licenseKeySecret: the-name-of-the-custom-secret +``` + +In the example above, we show how you can configure the agent settings globally using environment variables. See each agent's configuration documentation for available configuration options: +* [Java](https://docs.newrelic.com/docs/apm/agents/java-agent/configuration/java-agent-configuration-config-file/) +* [Node](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/) +* [Python](https://docs.newrelic.com/docs/apm/agents/python-agent/configuration/python-agent-configuration/) +* [.NET](https://docs.newrelic.com/docs/apm/agents/net-agent/configuration/net-agent-configuration/) +* [Ruby](https://docs.newrelic.com/docs/apm/agents/ruby-agent/configuration/ruby-agent-configuration/) + +### cert-manager + +The K8s Agents Operator supports the use of [`cert-manager`](https://github.com/cert-manager/cert-manager) if preferred. + +Install the [`cert-manager`](https://github.com/cert-manager/cert-manager) Helm chart: +```shell +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set crds.enabled=true +``` + +In your `values.yaml` file, set `admissionWebhooks.autoGenerateCert.enabled: false` and `admissionWebhooks.certManager.enabled: true`. Then install the chart as normal. + +## Available Chart Releases + +To see the available charts: +```shell +helm search repo k8s-agents-operator +``` + +If you want to see a list of all available charts and releases, check [index.yaml](https://newrelic.github.io/k8s-agents-operator/index.yaml). + +## Source Code + +* + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| https://helm-charts.newrelic.com | common-library | 1.3.0 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| admissionWebhooks | object | `{"autoGenerateCert":{"certPeriodDays":365,"enabled":true,"recreate":true},"caFile":"","certFile":"","certManager":{"enabled":false},"create":true,"keyFile":""}` | Admission webhooks make sure only requests with correctly formatted rules will get into the Operator | +| admissionWebhooks.autoGenerateCert.certPeriodDays | int | `365` | Cert validity period time in days. | +| admissionWebhooks.autoGenerateCert.enabled | bool | `true` | If true and certManager.enabled is false, Helm will automatically create a self-signed cert and secret for you. | +| admissionWebhooks.autoGenerateCert.recreate | bool | `true` | If set to true, new webhook key/certificate is generated on helm upgrade. | +| admissionWebhooks.caFile | string | `""` | Path to the CA cert. | +| admissionWebhooks.certFile | string | `""` | Path to your own PEM-encoded certificate. | +| admissionWebhooks.certManager.enabled | bool | `false` | If true and autoGenerateCert.enabled is false, cert-manager will create a self-signed cert and secret for you. | +| admissionWebhooks.keyFile | string | `""` | Path to your own PEM-encoded private key. | +| controllerManager.kubeRbacProxy.image.repository | string | `"gcr.io/kubebuilder/kube-rbac-proxy"` | | +| controllerManager.kubeRbacProxy.image.tag | string | `"v0.14.0"` | | +| controllerManager.kubeRbacProxy.resources.limits.cpu | string | `"500m"` | | +| controllerManager.kubeRbacProxy.resources.limits.memory | string | `"128Mi"` | | +| controllerManager.kubeRbacProxy.resources.requests.cpu | string | `"5m"` | | +| controllerManager.kubeRbacProxy.resources.requests.memory | string | `"64Mi"` | | +| controllerManager.manager.image.pullPolicy | string | `nil` | | +| controllerManager.manager.image.repository | string | `"newrelic/k8s-agents-operator"` | | +| controllerManager.manager.image.tag | string | `nil` | | +| controllerManager.manager.leaderElection | object | `{"enabled":true}` | Enable leader election mechanism for protecting against split brain if multiple operator pods/replicas are started | +| controllerManager.manager.resources.requests.cpu | string | `"100m"` | | +| controllerManager.manager.resources.requests.memory | string | `"64Mi"` | | +| controllerManager.manager.serviceAccount.create | bool | `true` | | +| controllerManager.replicas | int | `1` | | +| kubernetesClusterDomain | string | `"cluster.local"` | | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| metricsService.ports[0].name | string | `"https"` | | +| metricsService.ports[0].port | int | `8443` | | +| metricsService.ports[0].protocol | string | `"TCP"` | | +| metricsService.ports[0].targetPort | string | `"https"` | | +| metricsService.type | string | `"ClusterIP"` | | +| securityContext | object | `{"fsGroup":65532,"runAsGroup":65532,"runAsNonRoot":true,"runAsUser":65532}` | SecurityContext holds pod-level security attributes and common container settings | +| webhookService.ports[0].port | int | `443` | | +| webhookService.ports[0].protocol | string | `"TCP"` | | +| webhookService.ports[0].targetPort | int | `9443` | | +| webhookService.type | string | `"ClusterIP"` | | + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| csongnr | | | +| dbudziwojskiNR | | | +| danielstokes | | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md.gotmpl new file mode 100644 index 000000000..be451d31c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/README.md.gotmpl @@ -0,0 +1,230 @@ +{{ template "chart.header" . }} + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +## Prerequisites + +[Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm's [documentation](https://helm.sh/docs) to get started. + +## Installation + +### Requirements + +Add the `k8s-agents-operator` Helm chart repository: +```shell +helm repo add k8s-agents-operator https://newrelic.github.io/k8s-agents-operator +``` + +### Instrumentation + +Install the [`k8s-agents-operator`](https://github.com/newrelic/k8s-agents-operator) Helm chart: +```shell +helm upgrade --install k8s-agents-operator k8s-agents-operator/k8s-agents-operator \ + --namespace newrelic \ + --create-namespace \ + --values your-custom-values.yaml +``` + +### Monitored namespaces + +For each namespace you want the operator to be instrumented, a secret will be replicated from the newrelic operator namespace. + +For each `Instrumentation` custom resource created, specifying which APM agent you want to instrument for each language. All available APM + agent docker images and corresponding tags are listed on DockerHub: + +* [.NET](https://hub.docker.com/repository/docker/newrelic/newrelic-dotnet-init/general) +* [Java](https://hub.docker.com/repository/docker/newrelic/newrelic-java-init/general) +* [Node](https://hub.docker.com/repository/docker/newrelic/newrelic-node-init/general) +* [Python](https://hub.docker.com/repository/docker/newrelic/newrelic-python-init/general) +* [Ruby](https://hub.docker.com/repository/docker/newrelic/newrelic-ruby-init/general) + +For .NET + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-dotnet +spec: + agent: + language: dotnet + image: newrelic/newrelic-dotnet-init:latest + # env: ... +``` + +For Java + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-java + namespace: newrelic +spec: + agent: + language: java + image: newrelic/newrelic-java-init:latest + # env: ... +``` + +For NodeJS + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-nodejs + namespace: newrelic +spec: + agent: + language: nodejs + image: newrelic/newrelic-node-init:latest + # env: ... +``` + +For Python + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-python + namespace: newrelic +spec: + agent: + language: python + image: newrelic/newrelic-python-init:latest + # env: ... +``` + +For Ruby + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-ruby + namespace: newrelic +spec: + agent: + language: ruby + image: newrelic/newrelic-ruby-init:latest + # env: ... +``` + +For environment specific configurations + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + agent: + env: + # Example New Relic agent supported environment variables + - name: NEW_RELIC_LABELS + value: "environment:auto-injection" + # Example setting the pod name based on the metadata + - name: NEW_RELIC_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + # Example overriding the appName configuration + - name: NEW_RELIC_APP_NAME + value: "$(NEW_RELIC_LABELS)-$(NEW_RELIC_POD_NAME)" +``` + +Targeting everything in a specific namespace with a label + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + #agent: ... + namespaceLabelSelector: + matchExpressions: + - key: "app.newrelic.instrumentation" + operator: "In" + values: ["java"] +``` + +Targeting a pod with a specific label + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + # agent: ... + podLabelSelector: + matchExpressions: + - key: "app.newrelic.instrumentation" + operator: "In" + values: ["dotnet"] +``` + +Using a secret with a non-default name + +```yaml +apiVersion: newrelic.com/v1alpha2 +kind: Instrumentation +metadata: + name: newrelic-instrumentation-lang + namespace: newrelic +spec: + # agent: ... + licenseKeySecret: the-name-of-the-custom-secret +``` + +In the example above, we show how you can configure the agent settings globally using environment variables. See each agent's configuration documentation for available configuration options: +* [Java](https://docs.newrelic.com/docs/apm/agents/java-agent/configuration/java-agent-configuration-config-file/) +* [Node](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/) +* [Python](https://docs.newrelic.com/docs/apm/agents/python-agent/configuration/python-agent-configuration/) +* [.NET](https://docs.newrelic.com/docs/apm/agents/net-agent/configuration/net-agent-configuration/) +* [Ruby](https://docs.newrelic.com/docs/apm/agents/ruby-agent/configuration/ruby-agent-configuration/) + +### cert-manager + +The K8s Agents Operator supports the use of [`cert-manager`](https://github.com/cert-manager/cert-manager) if preferred. + +Install the [`cert-manager`](https://github.com/cert-manager/cert-manager) Helm chart: +```shell +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set crds.enabled=true +``` + +In your `values.yaml` file, set `admissionWebhooks.autoGenerateCert.enabled: false` and `admissionWebhooks.certManager.enabled: true`. Then install the chart as normal. + +## Available Chart Releases + +To see the available charts: +```shell +helm search repo k8s-agents-operator +``` + +If you want to see a list of all available charts and releases, check [index.yaml](https://newrelic.github.io/k8s-agents-operator/index.yaml). + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +{{ template "chart.maintainersSection" . }} + +{{ template "helm-docs.versionFooter" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/NOTES.txt new file mode 100644 index 000000000..f5d1cb647 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/NOTES.txt @@ -0,0 +1,36 @@ +This project is currently in preview. +Issues and contributions should be reported to the project's GitHub. +{{- if (include "k8s-agents-operator.areValuesValid" .) }} +===================================== + + ******** + **************** + ********** **********, + &&&**** ****/((( + &&&&&&& (((((( + &&&&&&&&&& (((((( + &&&&&&&& (((((( + &&&&& (((((( + &&&&& (((((((( + &&&&& .(((((((((( + &&&&&(((((((( + &&&(((, + +Your deployment of the New Relic Agent Operator is complete. +You can check on the progress of this by running the following command: + +kubectl get deployments -o wide -w --namespace {{ .Release.Namespace }} {{ include "newrelic.common.naming.fullname" . }} + +WARNING: This deployment will be incomplete until you configure your Instrumentation custom resource definition. +===================================== + +Please visit https://github.com/newrelic/k8s-agents-operator for instructions on how to create & configure the +Instrumentation custom resource definition required by the Operator. +{{- else }} + +############################################################################## +#### ERROR: You did not set a license key. #### +############################################################################## + +This deployment will be incomplete until you get your ingest license key from New Relic. +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_helpers.tpl new file mode 100644 index 000000000..bec72aa55 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_helpers.tpl @@ -0,0 +1,7 @@ +{{/* +Returns if the template should render, it checks if the required values are set. +*/}} +{{- define "k8s-agents-operator.areValuesValid" -}} +{{- $licenseKey := include "newrelic.common.license._licenseKey" . -}} +{{- and (or $licenseKey)}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_naming.tpl new file mode 100644 index 000000000..e32d3e3e1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_naming.tpl @@ -0,0 +1,52 @@ +{{/* Controller manager service certificate's secret. */}} +{{- define "k8s-agents-operator.certificateSecret.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "controller-manager-service-cert") -}} +{{- end }} + +{{- define "k8s-agents-operator.webhook.service.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "webhook-service") -}} +{{- end -}} + +{{- define "k8s-agents-operator.webhook.mutating.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "mutation") -}} +{{- end -}} + +{{- define "k8s-agents-operator.webhook.validating.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "validation") -}} +{{- end -}} + +{{- define "k8s-agents-operator.cert-manager.issuer.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "selfsigned-issuer") -}} +{{- end -}} + +{{- define "k8s-agents-operator.cert-manager.certificate.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "serving-cert") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.proxy.role.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "proxy-role") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.proxy.roleBinding.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "proxy-rolebinding") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.manager.role.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "manager-role") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.manager.roleBinding.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "manager-rolebinding") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.leaderElection.role.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "leader-election-role") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.leaderElection.roleBinding.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "leader-election-rolebinding") -}} +{{- end -}} + +{{- define "k8s-agents-operator.rbac.reader.role.name" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "metrics-reader") -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_tls.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_tls.tpl new file mode 100644 index 000000000..b57e03cfc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/_tls.tpl @@ -0,0 +1,40 @@ +{{/* +Return certificate and CA for Webhooks. +It handles variants when a cert has to be generated by Helm, +a cert is loaded from an existing secret or is provided via `.Values` +*/}} +{{- define "k8s-agents-operator.webhookCert" -}} +{{- $caCert := "" }} +{{- $clientCert := "" }} +{{- $clientKey := "" }} +{{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} + {{- $prevSecret := (lookup "v1" "Secret" .Release.Namespace (include "k8s-agents-operator.certificateSecret.name" . )) }} + {{- if and (not .Values.admissionWebhooks.autoGenerateCert.recreate) $prevSecret }} + {{- $clientCert = index $prevSecret "data" "tls.crt" }} + {{- $clientKey = index $prevSecret "data" "tls.key" }} + {{- $caCert = index $prevSecret "data" "ca.crt" }} + {{- if not $caCert }} + {{- $prevHook := (lookup "admissionregistration.k8s.io/v1" "MutatingWebhookConfiguration" .Release.Namespace (print (include "newrelic.common.naming.fullname" . ) "-mutation")) }} + {{- if not (eq (toString $prevHook) "") }} + {{- $caCert = (first $prevHook.webhooks).clientConfig.caBundle }} + {{- end }} + {{- end }} + {{- else }} + {{- $certValidity := int .Values.admissionWebhooks.autoGenerateCert.certPeriodDays | default 365 }} + {{- $ca := genCA "k8s-agents-operator-operator-ca" $certValidity }} + {{- $domain1 := printf "%s.%s.svc" (include "k8s-agents-operator.webhook.service.name" .) $.Release.Namespace }} + {{- $domain2 := printf "%s.%s.svc.%s" (include "k8s-agents-operator.webhook.service.name" .) $.Release.Namespace $.Values.kubernetesClusterDomain }} + {{- $domains := list $domain1 $domain2 }} + {{- $cert := genSignedCert (include "newrelic.common.naming.fullname" .) nil $domains $certValidity $ca }} + {{- $clientCert = b64enc $cert.Cert }} + {{- $clientKey = b64enc $cert.Key }} + {{- $caCert = b64enc $ca.Cert }} + {{- end }} +{{- else }} + {{- $clientCert = .Files.Get .Values.admissionWebhooks.certFile | b64enc }} + {{- $clientKey = .Files.Get .Values.admissionWebhooks.keyFile | b64enc }} + {{- $caCert = .Files.Get .Values.admissionWebhooks.caFile | b64enc }} +{{- end }} +{{- $result := dict "clientCert" $clientCert "clientKey" $clientKey "caCert" $caCert }} +{{- $result | toYaml }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/certmanager.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/certmanager.yaml new file mode 100644 index 000000000..048d87000 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/certmanager.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.admissionWebhooks.create .Values.admissionWebhooks.certManager.enabled }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "k8s-agents-operator.cert-manager.certificate.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + dnsNames: + - '{{ include "k8s-agents-operator.webhook.service.name" . }}.{{ .Release.Namespace }}.svc' + - '{{ include "k8s-agents-operator.webhook.service.name" . }}.{{ .Release.Namespace }}.svc.{{ .Values.kubernetesClusterDomain }}' + issuerRef: + kind: Issuer + name: {{ include "k8s-agents-operator.cert-manager.issuer.name" . }} + secretName: {{ include "k8s-agents-operator.certificateSecret.name" . }} + subject: + organizationalUnits: + - k8s-agents-operator +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "k8s-agents-operator.cert-manager.issuer.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selfSigned: {} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/deployment.yaml new file mode 100644 index 000000000..2048383ff --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/deployment.yaml @@ -0,0 +1,95 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + control-plane: controller-manager + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.controllerManager.replicas }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + control-plane: controller-manager + template: + metadata: + labels: + control-plane: controller-manager + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + containers: + - args: + - --metrics-addr=127.0.0.1:8080 + {{- if .Values.controllerManager.manager.leaderElection.enabled }} + - --enable-leader-election + {{- end }} + - --zap-log-level=info + - --zap-time-encoding=rfc3339nano + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: KUBERNETES_CLUSTER_DOMAIN + value: {{ quote .Values.kubernetesClusterDomain }} + - name: ENABLE_WEBHOOKS + value: "true" + image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.controllerManager.manager.image.pullPolicy | default "Always" }} + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: {{- toYaml .Values.controllerManager.manager.resources | nindent 10 }} + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + env: + - name: KUBERNETES_CLUSTER_DOMAIN + value: {{ quote .Values.kubernetesClusterDomain }} + image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag | default .Chart.AppVersion }} + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + protocol: TCP + resources: {{- toYaml .Values.controllerManager.kubeRbacProxy.resources | nindent 10 }} + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + terminationGracePeriodSeconds: 10 + {{- if or .Values.admissionWebhooks.create (include "k8s-agents-operator.certificateSecret.name" . ) }} + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: {{ include "k8s-agents-operator.certificateSecret.name" . }} + {{- end }} + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/instrumentation-crd.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/instrumentation-crd.yaml new file mode 100644 index 000000000..68dbde935 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/instrumentation-crd.yaml @@ -0,0 +1,407 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: instrumentations.newrelic.com + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + group: newrelic.com + names: + kind: Instrumentation + listKind: InstrumentationList + plural: instrumentations + shortNames: + - nragent + - nragents + singular: instrumentation + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: Instrumentation is the Schema for the instrumentations 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: InstrumentationSpec defines the desired state of Instrumentation + properties: + agent: + description: Agent defines configuration for agent instrumentation. + properties: + env: + description: |- + Env defines Go specific env vars. There are four layers for env vars' definitions and + the precedence order is: `original container env vars` > `language specific env vars` > `common env vars` > `instrument spec configs' vars`. + If the former var had been defined, then the other vars would be ignored. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the FieldPath is + written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image is a container image with Go SDK and auto-instrumentation. + type: string + language: + description: Language is the language that will be instrumented. + type: string + resourceRequirements: + description: Resources describes the compute resource requirements. + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + volumeLimitSize: + anyOf: + - type: integer + - type: string + description: |- + VolumeSizeLimit defines size limit for volume used for auto-instrumentation. + The default size is 200Mi. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + exporter: + description: Exporter defines exporter configuration. + properties: + endpoint: + description: Endpoint is address of the collector with OTLP endpoint. + type: string + type: object + licenseKeySecret: + description: |- + LicenseKeySecret defines where to take the licenseKeySecret. + it should be present in the operator namespace. + type: string + namespaceLabelSelector: + description: PodLabelSelector defines to which pods the config should + be applied. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + podLabelSelector: + description: PodLabelSelector defines to which pods the config should + be applied. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + propagators: + description: |- + Propagators defines inter-process context propagation configuration. + Values in this list will be set in the OTEL_PROPAGATORS env var. + Enum=tracecontext;none + items: + description: Propagator represents the propagation type. + enum: + - tracecontext + - none + type: string + type: array + resource: + description: Resource defines the configuration for the resource attributes, + as defined by the OpenTelemetry specification. + properties: + addK8sUIDAttributes: + description: AddK8sUIDAttributes defines whether K8s UID attributes + should be collected (e.g. k8s.deployment.uid). + type: boolean + resourceAttributes: + additionalProperties: + type: string + description: |- + Attributes defines attributes that are added to the resource. + For example environment: dev + type: object + type: object + sampler: + description: Sampler defines sampling configuration. + properties: + argument: + description: |- + Argument defines sampler argument. + The value depends on the sampler type. + For instance for parentbased_traceidratio sampler type it is a number in range [0..1] e.g. 0.25. + The value will be set in the OTEL_TRACES_SAMPLER_ARG env var. + type: string + type: + description: |- + Type defines sampler type. + The value will be set in the OTEL_TRACES_SAMPLER env var. + The value can be for instance parentbased_always_on, parentbased_always_off, parentbased_traceidratio... + enum: + - always_on + - always_off + - traceidratio + - parentbased_always_on + - parentbased_always_off + - parentbased_traceidratio + type: string + type: object + type: object + status: + description: InstrumentationStatus defines the observed state of Instrumentation + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/leader-election-rbac.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/leader-election-rbac.yaml new file mode 100644 index 000000000..65111dd95 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/leader-election-rbac.yaml @@ -0,0 +1,51 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "k8s-agents-operator.rbac.leaderElection.role.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - configmaps/status + verbs: + - get + - update + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "k8s-agents-operator.rbac.leaderElection.roleBinding.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "k8s-agents-operator.rbac.leaderElection.role.name" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/manager-rbac.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/manager-rbac.yaml new file mode 100644 index 000000000..a292d931a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/manager-rbac.yaml @@ -0,0 +1,88 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "k8s-agents-operator.rbac.manager.role.name" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: [ "" ] + resources: ["secrets"] + verbs: + - get + - list + - create + - delete + - deletecollection + - patch + - update + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - list + - watch +- apiGroups: + - apps + resources: + - replicasets + verbs: + - get + - list + - watch +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - list + - update +- apiGroups: + - newrelic.com + resources: + - instrumentations + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "k8s-agents-operator.rbac.manager.roleBinding.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "k8s-agents-operator.rbac.manager.role.name" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/newrelic_license_secret.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/proxy-rbac.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/proxy-rbac.yaml new file mode 100644 index 000000000..47f300926 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/proxy-rbac.yaml @@ -0,0 +1,35 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "k8s-agents-operator.rbac.proxy.role.name" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "k8s-agents-operator.rbac.proxy.roleBinding.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "k8s-agents-operator.rbac.proxy.role.name" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/reader-rbac.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/reader-rbac.yaml new file mode 100644 index 000000000..dc1ac527f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/reader-rbac.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "k8s-agents-operator.rbac.reader.role.name" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/secret.yaml new file mode 100644 index 000000000..b437d4f14 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/secret.yaml @@ -0,0 +1,19 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: newrelic-key-secret + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + new_relic_license_key: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/service.yaml new file mode 100644 index 000000000..427f60f08 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + control-plane: controller-manager + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + type: {{ .Values.metricsService.type }} + selector: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} + control-plane: controller-manager + ports: + {{- .Values.metricsService.ports | toYaml | nindent 2 -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-configuration.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-configuration.yaml new file mode 100644 index 000000000..a32941061 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-configuration.yaml @@ -0,0 +1,134 @@ +{{- $tls := fromYaml (include "k8s-agents-operator.webhookCert" .) }} +{{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: {{ include "k8s-agents-operator.certificateSecret.name" . }} + annotations: + "helm.sh/hook": "pre-install,pre-upgrade" + "helm.sh/hook-delete-policy": "before-hook-creation" + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + app.kubernetes.io/component: webhook + namespace: {{ .Release.Namespace }} +data: + tls.crt: {{ $tls.clientCert }} + tls.key: {{ $tls.clientKey }} + ca.crt: {{ $tls.caCert }} +{{- end }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "k8s-agents-operator.webhook.mutating.name" . }} + {{- if .Values.admissionWebhooks.certManager.enabled }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "k8s-agents-operator.cert-manager.certificate.name" . }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + {{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} + caBundle: {{ $tls.caCert }} + {{- end }} + service: + name: {{ include "k8s-agents-operator.webhook.service.name" . }} + namespace: {{ .Release.Namespace }} + path: /mutate-newrelic-com-v1alpha2-instrumentation + failurePolicy: Fail + name: minstrumentation.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - instrumentations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + {{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} + caBundle: {{ $tls.caCert }} + {{- end }} + service: + name: {{ include "k8s-agents-operator.webhook.service.name" . }} + namespace: {{ .Release.Namespace }} + path: /mutate-v1-pod + failurePolicy: Ignore + name: mpod.kb.io + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - pods + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ include "k8s-agents-operator.webhook.validating.name" . }} + {{- if .Values.admissionWebhooks.certManager.enabled }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "k8s-agents-operator.cert-manager.certificate.name" . }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + {{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} + caBundle: {{ $tls.caCert }} + {{- end }} + service: + name: {{ include "k8s-agents-operator.webhook.service.name" . }} + namespace: {{ .Release.Namespace }} + path: /validate-newrelic-com-v1alpha2-instrumentation + failurePolicy: Fail + name: vinstrumentationcreateupdate.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha2 + operations: + - CREATE + - UPDATE + resources: + - instrumentations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + {{- if .Values.admissionWebhooks.autoGenerateCert.enabled }} + caBundle: {{ $tls.caCert }} + {{- end }} + service: + name: {{ include "k8s-agents-operator.webhook.service.name" . }} + namespace: {{ .Release.Namespace }} + path: /validate-newrelic-com-v1alpha2-instrumentation + failurePolicy: Ignore + name: vinstrumentationdelete.kb.io + rules: + - apiGroups: + - newrelic.com + apiVersions: + - v1alpha2 + operations: + - DELETE + resources: + - instrumentations + sideEffects: None diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-service.yaml new file mode 100644 index 000000000..cb649dcf6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/templates/webhook-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "k8s-agents-operator.webhook.service.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + type: {{ .Values.webhookService.type }} + selector: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} + control-plane: controller-manager + ports: + {{- .Values.webhookService.ports | toYaml | nindent 2 -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/cert_manager_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/cert_manager_test.yaml new file mode 100644 index 000000000..1de201921 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/cert_manager_test.yaml @@ -0,0 +1,85 @@ +suite: cert-manager +templates: + - templates/certmanager.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: creates cert-manager resources if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - hasDocuments: + count: 2 + - it: creates Issuer if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - equal: + path: kind + value: Issuer + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-selfsigned-issuer + - exists: + path: spec.selfSigned + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-selfsigned-issuer + - it: creates Certificate in default domain if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - equal: + path: kind + value: Certificate + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-serving-cert + - equal: + path: spec.dnsNames + value: + - my-release-k8s-agents-operator-webhook-service.my-namespace.svc + - my-release-k8s-agents-operator-webhook-service.my-namespace.svc.cluster.local + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-serving-cert + - it: creates Certificate in custom domain if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + kubernetesClusterDomain: kubey.test + asserts: + - equal: + path: kind + value: Certificate + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-serving-cert + - equal: + path: spec.dnsNames + value: + - my-release-k8s-agents-operator-webhook-service.my-namespace.svc + - my-release-k8s-agents-operator-webhook-service.my-namespace.svc.kubey.test + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-serving-cert diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/webhook_ssl_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/webhook_ssl_test.yaml new file mode 100644 index 000000000..9343a43a4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/tests/webhook_ssl_test.yaml @@ -0,0 +1,176 @@ +suite: webhook ssl +templates: + - templates/webhook-configuration.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: creates ssl certificate secret by default + set: + licenseKey: us-whatever + asserts: + - hasDocuments: + count: 3 + - containsDocument: + kind: Secret + apiVersion: v1 + name: my-release-k8s-agents-operator-controller-manager-service-cert + namespace: my-namespace + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-controller-manager-service-cert + - exists: + path: data["tls.crt"] + template: templates/webhook-configuration.yaml + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-controller-manager-service-cert + - exists: + path: data["tls.key"] + template: templates/webhook-configuration.yaml + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-controller-manager-service-cert + - exists: + path: data["ca.crt"] + template: templates/webhook-configuration.yaml + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-controller-manager-service-cert + - it: does not inject cert-manager annotations into MutatingWebhook by default + set: + licenseKey: us-whatever + asserts: + - notExists: + path: metadata.annotations["cert-manager.io/inject-ca-from"] + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - it: does not inject cert-manager annotations into ValidatingWebhook by default + set: + licenseKey: us-whatever + asserts: + - notExists: + path: metadata.annotations["cert-manager.io/inject-ca-from"] + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-validation + - it: does inject caBundle into MutatingWebhook clientConfigs by default + set: + licenseKey: us-whatever + asserts: + - lengthEqual: + path: webhooks + count: 2 + - exists: + path: webhooks[0].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - exists: + path: webhooks[1].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - it: does inject caBundle into ValidatingWebhook clientConfigs by default + set: + licenseKey: us-whatever + asserts: + - lengthEqual: + path: webhooks + count: 2 + - exists: + path: webhooks[0].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - exists: + path: webhooks[1].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-validation + - it: does not creates ssl certificate secret if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - hasDocuments: + count: 2 + - it: injects cert-manager annotations into MutatingWebhook if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - equal: + path: metadata.annotations["cert-manager.io/inject-ca-from"] + value: my-namespace/my-release-k8s-agents-operator-serving-cert + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - it: injects cert-manager annotations into ValidatingWebhook if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - equal: + path: metadata.annotations["cert-manager.io/inject-ca-from"] + value: my-namespace/my-release-k8s-agents-operator-serving-cert + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-validation + - it: does not inject caBundle into MutatingWebhook clientConfigs if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - lengthEqual: + path: webhooks + count: 2 + - notExists: + path: webhooks[0].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - notExists: + path: webhooks[1].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - it: does not inject caBundle into ValidatingWebhook clientConfigs if cert-manager enabled and auto cert disabled + set: + licenseKey: us-whatever + admissionWebhooks: + autoGenerateCert: + enabled: false + certManager: + enabled: true + asserts: + - lengthEqual: + path: webhooks + count: 2 + - notExists: + path: webhooks[0].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-mutation + - notExists: + path: webhooks[1].clientConfig.caBundle + documentSelector: + path: metadata.name + value: my-release-k8s-agents-operator-validation diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/values.yaml new file mode 100644 index 000000000..f28979778 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/k8s-agents-operator/values.yaml @@ -0,0 +1,93 @@ +# Default values for k8s-agents-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" + +controllerManager: + replicas: 1 + + kubeRbacProxy: + image: + repository: gcr.io/kubebuilder/kube-rbac-proxy + tag: v0.14.0 + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + + manager: + image: + repository: newrelic/k8s-agents-operator + tag: + pullPolicy: + resources: + requests: + cpu: 100m + memory: 64Mi + serviceAccount: + create: true + # -- Source: https://docs.openshift.com/container-platform/4.10/operators/operator_sdk/osdk-leader-election.html + # -- Enable leader election mechanism for protecting against split brain if multiple operator pods/replicas are started + leaderElection: + enabled: true + +kubernetesClusterDomain: cluster.local + +metricsService: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + type: ClusterIP + +webhookService: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + type: ClusterIP + +# -- Source: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +# -- SecurityContext holds pod-level security attributes and common container settings +securityContext: + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + +# -- Admission webhooks make sure only requests with correctly formatted rules will get into the Operator +admissionWebhooks: + create: true + + ## TLS Certificate Option 1: Use Helm to automatically generate self-signed certificate. + ## certManager must be disabled and autoGenerateCert must be enabled. + autoGenerateCert: + # -- If true and certManager.enabled is false, Helm will automatically create a self-signed cert and secret for you. + enabled: true + # -- If set to true, new webhook key/certificate is generated on helm upgrade. + recreate: true + # -- Cert validity period time in days. + certPeriodDays: 365 + + ## TLS Certificate Option 2: Use certManager to generate self-signed certificate. + certManager: + # -- If true and autoGenerateCert.enabled is false, cert-manager will create a self-signed cert and secret for you. + enabled: false + + ## TLS Certificate Option 3: Use your own self-signed certificate. + ## certManager and autoGenerateCert must be disabled and certFile, keyFile, and caFile must be set. + ## The chart reads the contents of the file paths with the helm .Files.Get function. + ## Refer to this doc https://helm.sh/docs/chart_template_guide/accessing_files/ to understand + ## limitations of file paths accessible to the chart. + # -- Path to your own PEM-encoded certificate. + certFile: "" + # -- Path to your own PEM-encoded private key. + keyFile: "" + # -- Path to the CA cert. + caFile: "" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/.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/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/Chart.yaml new file mode 100644 index 000000000..755213319 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/Chart.yaml @@ -0,0 +1,26 @@ +annotations: + artifacthub.io/license: Apache-2.0 + artifacthub.io/links: | + - name: Chart Source + url: https://github.com/prometheus-community/helm-charts +apiVersion: v2 +appVersion: 2.13.0 +description: Install kube-state-metrics to generate and expose cluster-level metrics +home: https://github.com/kubernetes/kube-state-metrics/ +keywords: +- metric +- monitoring +- prometheus +- kubernetes +maintainers: +- email: tariq.ibrahim@mulesoft.com + name: tariq1890 +- email: manuel@rueg.eu + name: mrueg +- email: david@0xdc.me + name: dotdc +name: kube-state-metrics +sources: +- https://github.com/kubernetes/kube-state-metrics/ +type: application +version: 5.26.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/README.md new file mode 100644 index 000000000..843be89e6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/README.md @@ -0,0 +1,85 @@ +# kube-state-metrics Helm Chart + +Installs the [kube-state-metrics agent](https://github.com/kubernetes/kube-state-metrics). + +## Get Repository Info + +```console +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update +``` + +_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._ + + +## Install Chart + +```console +helm install [RELEASE_NAME] prometheus-community/kube-state-metrics [flags] +``` + +_See [configuration](#configuration) below._ + +_See [helm install](https://helm.sh/docs/helm/helm_install/) for command documentation._ + +## Uninstall Chart + +```console +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._ + +## Upgrading Chart + +```console +helm upgrade [RELEASE_NAME] prometheus-community/kube-state-metrics [flags] +``` + +_See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command documentation._ + +### Migrating from stable/kube-state-metrics and kubernetes/kube-state-metrics + +You can upgrade in-place: + +1. [get repository info](#get-repository-info) +1. [upgrade](#upgrading-chart) your existing release name using the new chart repository + +## Upgrading to v3.0.0 + +v3.0.0 includes kube-state-metrics v2.0, see the [changelog](https://github.com/kubernetes/kube-state-metrics/blob/release-2.0/CHANGELOG.md) for major changes on the application-side. + +The upgraded chart now the following changes: + +* Dropped support for helm v2 (helm v3 or later is required) +* collectors key was renamed to resources +* namespace key was renamed to namespaces + +## 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: + +```console +helm show values prometheus-community/kube-state-metrics +``` + +### kube-rbac-proxy + +You can enable `kube-state-metrics` endpoint protection using `kube-rbac-proxy`. By setting `kubeRBACProxy.enabled: true`, this chart will deploy one RBAC proxy container per endpoint (metrics & telemetry). +To authorize access, authenticate your requests (via a `ServiceAccount` for example) with a `ClusterRole` attached such as: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: kube-state-metrics-read +rules: + - apiGroups: [ "" ] + resources: ["services/kube-state-metrics"] + verbs: + - get +``` + +See [kube-rbac-proxy examples](https://github.com/brancz/kube-rbac-proxy/tree/master/examples/resource-attributes) for more details. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/NOTES.txt new file mode 100644 index 000000000..3589c24ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/NOTES.txt @@ -0,0 +1,23 @@ +kube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects. +The exposed metrics can be found here: +https://github.com/kubernetes/kube-state-metrics/blob/master/docs/README.md#exposed-metrics + +The metrics are exported on the HTTP endpoint /metrics on the listening port. +In your case, {{ template "kube-state-metrics.fullname" . }}.{{ template "kube-state-metrics.namespace" . }}.svc.cluster.local:{{ .Values.service.port }}/metrics + +They are served either as plaintext or protobuf depending on the Accept header. +They are designed to be consumed either by Prometheus itself or by a scraper that is compatible with scraping a Prometheus client endpoint. + +{{- if .Values.kubeRBACProxy.enabled}} + +kube-rbac-proxy endpoint protections is enabled: +- Metrics endpoints are now HTTPS +- Ensure that the client authenticates the requests (e.g. via service account) with the following role permissions: +``` +rules: + - apiGroups: [ "" ] + resources: ["services/{{ template "kube-state-metrics.fullname" . }}"] + verbs: + - get +``` +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/_helpers.tpl new file mode 100644 index 000000000..3dd326da4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/_helpers.tpl @@ -0,0 +1,156 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "kube-state-metrics.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 "kube-state-metrics.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 the name of the service account to use +*/}} +{{- define "kube-state-metrics.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "kube-state-metrics.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts +*/}} +{{- define "kube-state-metrics.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "kube-state-metrics.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Generate basic labels +*/}} +{{- define "kube-state-metrics.labels" }} +helm.sh/chart: {{ template "kube-state-metrics.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/component: metrics +app.kubernetes.io/part-of: {{ template "kube-state-metrics.name" . }} +{{- include "kube-state-metrics.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +{{- if .Values.customLabels }} +{{ tpl (toYaml .Values.customLabels) . }} +{{- end }} +{{- if .Values.releaseLabel }} +release: {{ .Release.Name }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "kube-state-metrics.selectorLabels" }} +{{- if .Values.selectorOverride }} +{{ toYaml .Values.selectorOverride }} +{{- else }} +app.kubernetes.io/name: {{ include "kube-state-metrics.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} +{{- end }} + +{{/* Sets default scrape limits for servicemonitor */}} +{{- define "servicemonitor.scrapeLimits" -}} +{{- with .sampleLimit }} +sampleLimit: {{ . }} +{{- end }} +{{- with .targetLimit }} +targetLimit: {{ . }} +{{- end }} +{{- with .labelLimit }} +labelLimit: {{ . }} +{{- end }} +{{- with .labelNameLengthLimit }} +labelNameLengthLimit: {{ . }} +{{- end }} +{{- with .labelValueLengthLimit }} +labelValueLengthLimit: {{ . }} +{{- end }} +{{- end -}} + +{{/* +Formats imagePullSecrets. Input is (dict "Values" .Values "imagePullSecrets" .{specific imagePullSecrets}) +*/}} +{{- define "kube-state-metrics.imagePullSecrets" -}} +{{- range (concat .Values.global.imagePullSecrets .imagePullSecrets) }} + {{- if eq (typeOf .) "map[string]interface {}" }} +- {{ toYaml . | trim }} + {{- else }} +- name: {{ . }} + {{- end }} +{{- end }} +{{- end -}} + +{{/* +The image to use for kube-state-metrics +*/}} +{{- define "kube-state-metrics.image" -}} +{{- if .Values.image.sha }} +{{- if .Values.global.imageRegistry }} +{{- printf "%s/%s:%s@%s" .Values.global.imageRegistry .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) .Values.image.sha }} +{{- else }} +{{- printf "%s/%s:%s@%s" .Values.image.registry .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) .Values.image.sha }} +{{- end }} +{{- else }} +{{- if .Values.global.imageRegistry }} +{{- printf "%s/%s:%s" .Values.global.imageRegistry .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) }} +{{- else }} +{{- printf "%s/%s:%s" .Values.image.registry .Values.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.image.tag) }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +The image to use for kubeRBACProxy +*/}} +{{- define "kubeRBACProxy.image" -}} +{{- if .Values.kubeRBACProxy.image.sha }} +{{- if .Values.global.imageRegistry }} +{{- printf "%s/%s:%s@%s" .Values.global.imageRegistry .Values.kubeRBACProxy.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.kubeRBACProxy.image.tag) .Values.kubeRBACProxy.image.sha }} +{{- else }} +{{- printf "%s/%s:%s@%s" .Values.kubeRBACProxy.image.registry .Values.kubeRBACProxy.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.kubeRBACProxy.image.tag) .Values.kubeRBACProxy.image.sha }} +{{- end }} +{{- else }} +{{- if .Values.global.imageRegistry }} +{{- printf "%s/%s:%s" .Values.global.imageRegistry .Values.kubeRBACProxy.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.kubeRBACProxy.image.tag) }} +{{- else }} +{{- printf "%s/%s:%s" .Values.kubeRBACProxy.image.registry .Values.kubeRBACProxy.image.repository (default (printf "v%s" .Chart.AppVersion) .Values.kubeRBACProxy.image.tag) }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml new file mode 100644 index 000000000..025cd47a8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/ciliumnetworkpolicy.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.networkPolicy.enabled (eq .Values.networkPolicy.flavor "cilium") }} +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + {{- if .Values.annotations }} + annotations: + {{ toYaml .Values.annotations | nindent 4 }} + {{- end }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +spec: + endpointSelector: + matchLabels: + {{- include "kube-state-metrics.selectorLabels" . | indent 6 }} + egress: + {{- if and .Values.networkPolicy.cilium .Values.networkPolicy.cilium.kubeApiServerSelector }} + {{ toYaml .Values.networkPolicy.cilium.kubeApiServerSelector | nindent 6 }} + {{- else }} + - toEntities: + - kube-apiserver + {{- end }} + ingress: + - toPorts: + - ports: + - port: {{ .Values.service.port | quote }} + protocol: TCP + {{- if .Values.selfMonitor.enabled }} + - port: {{ .Values.selfMonitor.telemetryPort | default 8081 | quote }} + protocol: TCP + {{ end }} +{{ end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..cf9f628d0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.rbac.create .Values.rbac.useClusterRole -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: {{ template "kube-state-metrics.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole +{{- if .Values.rbac.useExistingRole }} + name: {{ .Values.rbac.useExistingRole }} +{{- else }} + name: {{ template "kube-state-metrics.fullname" . }} +{{- end }} +subjects: +- kind: ServiceAccount + name: {{ template "kube-state-metrics.serviceAccountName" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/crs-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/crs-configmap.yaml new file mode 100644 index 000000000..d38a75a51 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/crs-configmap.yaml @@ -0,0 +1,16 @@ +{{- if .Values.customResourceState.enabled}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "kube-state-metrics.fullname" . }}-customresourcestate-config + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + {{- if .Values.annotations }} + annotations: + {{ toYaml .Values.annotations | nindent 4 }} + {{- end }} +data: + config.yaml: | + {{- toYaml .Values.customResourceState.config | nindent 4 }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/deployment.yaml new file mode 100644 index 000000000..2aff18888 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/deployment.yaml @@ -0,0 +1,336 @@ +apiVersion: apps/v1 +{{- if .Values.autosharding.enabled }} +kind: StatefulSet +{{- else }} +kind: Deployment +{{- end }} +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + {{- if .Values.annotations }} + annotations: +{{ toYaml .Values.annotations | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "kube-state-metrics.selectorLabels" . | indent 6 }} + replicas: {{ .Values.replicas }} + {{- if not .Values.autosharding.enabled }} + strategy: + type: {{ .Values.updateStrategy | default "RollingUpdate" }} + {{- end }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + {{- if .Values.autosharding.enabled }} + serviceName: {{ template "kube-state-metrics.fullname" . }} + volumeClaimTemplates: [] + {{- end }} + template: + metadata: + labels: + {{- include "kube-state-metrics.labels" . | indent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.podAnnotations }} + annotations: + {{ toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + spec: + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + hostNetwork: {{ .Values.hostNetwork }} + serviceAccountName: {{ template "kube-state-metrics.serviceAccountName" . }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + {{- with .Values.initContainers }} + initContainers: + {{- toYaml . | nindent 6 }} + {{- end }} + containers: + {{- $servicePort := ternary 9090 (.Values.service.port | default 8080) .Values.kubeRBACProxy.enabled}} + {{- $telemetryPort := ternary 9091 (.Values.selfMonitor.telemetryPort | default 8081) .Values.kubeRBACProxy.enabled}} + - name: {{ template "kube-state-metrics.name" . }} + {{- if .Values.autosharding.enabled }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- end }} + args: + {{- if .Values.extraArgs }} + {{- .Values.extraArgs | toYaml | nindent 8 }} + {{- end }} + - --port={{ $servicePort }} + {{- if .Values.collectors }} + - --resources={{ .Values.collectors | join "," }} + {{- end }} + {{- if .Values.metricLabelsAllowlist }} + - --metric-labels-allowlist={{ .Values.metricLabelsAllowlist | join "," }} + {{- end }} + {{- if .Values.metricAnnotationsAllowList }} + - --metric-annotations-allowlist={{ .Values.metricAnnotationsAllowList | join "," }} + {{- end }} + {{- if .Values.metricAllowlist }} + - --metric-allowlist={{ .Values.metricAllowlist | join "," }} + {{- end }} + {{- if .Values.metricDenylist }} + - --metric-denylist={{ .Values.metricDenylist | join "," }} + {{- end }} + {{- $namespaces := list }} + {{- if .Values.namespaces }} + {{- range $ns := join "," .Values.namespaces | split "," }} + {{- $namespaces = append $namespaces (tpl $ns $) }} + {{- end }} + {{- end }} + {{- if .Values.releaseNamespace }} + {{- $namespaces = append $namespaces ( include "kube-state-metrics.namespace" . ) }} + {{- end }} + {{- if $namespaces }} + - --namespaces={{ $namespaces | mustUniq | join "," }} + {{- end }} + {{- if .Values.namespacesDenylist }} + - --namespaces-denylist={{ tpl (.Values.namespacesDenylist | join ",") $ }} + {{- end }} + {{- if .Values.autosharding.enabled }} + - --pod=$(POD_NAME) + - --pod-namespace=$(POD_NAMESPACE) + {{- end }} + {{- if .Values.kubeconfig.enabled }} + - --kubeconfig=/opt/k8s/.kube/config + {{- end }} + {{- if .Values.kubeRBACProxy.enabled }} + - --telemetry-host=127.0.0.1 + - --telemetry-port={{ $telemetryPort }} + {{- else }} + {{- if .Values.selfMonitor.telemetryHost }} + - --telemetry-host={{ .Values.selfMonitor.telemetryHost }} + {{- end }} + {{- if .Values.selfMonitor.telemetryPort }} + - --telemetry-port={{ $telemetryPort }} + {{- end }} + {{- end }} + {{- if .Values.customResourceState.enabled }} + - --custom-resource-state-config-file=/etc/customresourcestate/config.yaml + {{- end }} + {{- if or (.Values.kubeconfig.enabled) (.Values.customResourceState.enabled) (.Values.volumeMounts) }} + volumeMounts: + {{- if .Values.kubeconfig.enabled }} + - name: kubeconfig + mountPath: /opt/k8s/.kube/ + readOnly: true + {{- end }} + {{- if .Values.customResourceState.enabled }} + - name: customresourcestate-config + mountPath: /etc/customresourcestate + readOnly: true + {{- end }} + {{- if .Values.volumeMounts }} +{{ toYaml .Values.volumeMounts | indent 8 }} + {{- end }} + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + image: {{ include "kube-state-metrics.image" . }} + {{- if eq .Values.kubeRBACProxy.enabled false }} + ports: + - containerPort: {{ .Values.service.port | default 8080}} + name: "http" + {{- if .Values.selfMonitor.enabled }} + - containerPort: {{ $telemetryPort }} + name: "metrics" + {{- end }} + {{- end }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + httpGet: + {{- if .Values.hostNetwork }} + host: 127.0.0.1 + {{- end }} + httpHeaders: + {{- range $_, $header := .Values.startupProbe.httpGet.httpHeaders }} + - name: {{ $header.name }} + value: {{ $header.value }} + {{- end }} + path: /healthz + port: {{ $servicePort }} + scheme: {{ upper .Values.startupProbe.httpGet.scheme }} + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + {{- end }} + livenessProbe: + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + httpGet: + {{- if .Values.hostNetwork }} + host: 127.0.0.1 + {{- end }} + httpHeaders: + {{- range $_, $header := .Values.livenessProbe.httpGet.httpHeaders }} + - name: {{ $header.name }} + value: {{ $header.value }} + {{- end }} + path: /livez + port: {{ $servicePort }} + scheme: {{ upper .Values.livenessProbe.httpGet.scheme }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + readinessProbe: + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + httpGet: + {{- if .Values.hostNetwork }} + host: 127.0.0.1 + {{- end }} + httpHeaders: + {{- range $_, $header := .Values.readinessProbe.httpGet.httpHeaders }} + - name: {{ $header.name }} + value: {{ $header.value }} + {{- end }} + path: /readyz + port: {{ $servicePort }} + scheme: {{ upper .Values.readinessProbe.httpGet.scheme }} + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + resources: +{{ toYaml .Values.resources | indent 10 }} +{{- if .Values.containerSecurityContext }} + securityContext: +{{ toYaml .Values.containerSecurityContext | indent 10 }} +{{- end }} + {{- if .Values.kubeRBACProxy.enabled }} + - name: kube-rbac-proxy-http + args: + {{- if .Values.kubeRBACProxy.extraArgs }} + {{- .Values.kubeRBACProxy.extraArgs | toYaml | nindent 8 }} + {{- end }} + - --secure-listen-address=:{{ .Values.service.port | default 8080}} + - --upstream=http://127.0.0.1:{{ $servicePort }}/ + - --proxy-endpoints-port=8888 + - --config-file=/etc/kube-rbac-proxy-config/config-file.yaml + volumeMounts: + - name: kube-rbac-proxy-config + mountPath: /etc/kube-rbac-proxy-config + {{- with .Values.kubeRBACProxy.volumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} + imagePullPolicy: {{ .Values.kubeRBACProxy.image.pullPolicy }} + image: {{ include "kubeRBACProxy.image" . }} + ports: + - containerPort: {{ .Values.service.port | default 8080}} + name: "http" + - containerPort: 8888 + name: "http-healthz" + readinessProbe: + httpGet: + scheme: HTTPS + port: 8888 + path: healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + {{- if .Values.kubeRBACProxy.resources }} + resources: +{{ toYaml .Values.kubeRBACProxy.resources | indent 10 }} +{{- end }} +{{- if .Values.kubeRBACProxy.containerSecurityContext }} + securityContext: +{{ toYaml .Values.kubeRBACProxy.containerSecurityContext | indent 10 }} +{{- end }} + {{- if .Values.selfMonitor.enabled }} + - name: kube-rbac-proxy-telemetry + args: + {{- if .Values.kubeRBACProxy.extraArgs }} + {{- .Values.kubeRBACProxy.extraArgs | toYaml | nindent 8 }} + {{- end }} + - --secure-listen-address=:{{ .Values.selfMonitor.telemetryPort | default 8081 }} + - --upstream=http://127.0.0.1:{{ $telemetryPort }}/ + - --proxy-endpoints-port=8889 + - --config-file=/etc/kube-rbac-proxy-config/config-file.yaml + volumeMounts: + - name: kube-rbac-proxy-config + mountPath: /etc/kube-rbac-proxy-config + {{- with .Values.kubeRBACProxy.volumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} + imagePullPolicy: {{ .Values.kubeRBACProxy.image.pullPolicy }} + image: {{ include "kubeRBACProxy.image" . }} + ports: + - containerPort: {{ .Values.selfMonitor.telemetryPort | default 8081 }} + name: "metrics" + - containerPort: 8889 + name: "metrics-healthz" + readinessProbe: + httpGet: + scheme: HTTPS + port: 8889 + path: healthz + initialDelaySeconds: 5 + timeoutSeconds: 5 + {{- if .Values.kubeRBACProxy.resources }} + resources: +{{ toYaml .Values.kubeRBACProxy.resources | indent 10 }} +{{- end }} +{{- if .Values.kubeRBACProxy.containerSecurityContext }} + securityContext: +{{ toYaml .Values.kubeRBACProxy.containerSecurityContext | indent 10 }} +{{- end }} + {{- end }} + {{- end }} + {{- with .Values.containers }} + {{- toYaml . | nindent 6 }} + {{- end }} +{{- if or .Values.imagePullSecrets .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- include "kube-state-metrics.imagePullSecrets" (dict "Values" .Values "imagePullSecrets" .Values.imagePullSecrets) | indent 8 }} + {{- end }} + {{- if .Values.affinity }} + affinity: +{{ toYaml .Values.affinity | indent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ tpl (toYaml .) $ | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ tpl (toYaml .) $ | indent 8 }} + {{- end }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.topologySpreadConstraints | indent 8 }} + {{- end }} + {{- if or (.Values.kubeconfig.enabled) (.Values.customResourceState.enabled) (.Values.volumes) (.Values.kubeRBACProxy.enabled) }} + volumes: + {{- if .Values.kubeconfig.enabled}} + - name: kubeconfig + secret: + secretName: {{ template "kube-state-metrics.fullname" . }}-kubeconfig + {{- end }} + {{- if .Values.kubeRBACProxy.enabled}} + - name: kube-rbac-proxy-config + configMap: + name: {{ template "kube-state-metrics.fullname" . }}-rbac-config + {{- end }} + {{- if .Values.customResourceState.enabled}} + - name: customresourcestate-config + configMap: + name: {{ template "kube-state-metrics.fullname" . }}-customresourcestate-config + {{- end }} + {{- if .Values.volumes }} +{{ toYaml .Values.volumes | indent 8 }} + {{- end }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/extra-manifests.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/extra-manifests.yaml new file mode 100644 index 000000000..567f7bf32 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/extra-manifests.yaml @@ -0,0 +1,4 @@ +{{ range .Values.extraManifests }} +--- +{{ tpl (toYaml .) $ }} +{{ end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/kubeconfig-secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/kubeconfig-secret.yaml new file mode 100644 index 000000000..6af008450 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/kubeconfig-secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.kubeconfig.enabled -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "kube-state-metrics.fullname" . }}-kubeconfig + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +type: Opaque +data: + config: '{{ .Values.kubeconfig.secret }}' +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/networkpolicy.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/networkpolicy.yaml new file mode 100644 index 000000000..309b38ec5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/networkpolicy.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.networkPolicy.enabled (eq .Values.networkPolicy.flavor "kubernetes") }} +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + {{- if .Values.annotations }} + annotations: + {{ toYaml .Values.annotations | nindent 4 }} + {{- end }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +spec: + {{- if .Values.networkPolicy.egress }} + ## Deny all egress by default + egress: + {{- toYaml .Values.networkPolicy.egress | nindent 4 }} + {{- end }} + ingress: + {{- if .Values.networkPolicy.ingress }} + {{- toYaml .Values.networkPolicy.ingress | nindent 4 }} + {{- else }} + ## Allow ingress on default ports by default + - ports: + - port: {{ .Values.service.port | default 8080 }} + protocol: TCP + {{- if .Values.selfMonitor.enabled }} + {{- $telemetryPort := ternary 9091 (.Values.selfMonitor.telemetryPort | default 8081) .Values.kubeRBACProxy.enabled}} + - port: {{ $telemetryPort }} + protocol: TCP + {{- end }} + {{- end }} + podSelector: + {{- if .Values.networkPolicy.podSelector }} + {{- toYaml .Values.networkPolicy.podSelector | nindent 4 }} + {{- else }} + matchLabels: + {{- include "kube-state-metrics.selectorLabels" . | indent 6 }} + {{- end }} + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/pdb.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/pdb.yaml new file mode 100644 index 000000000..3771b511d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/pdb.yaml @@ -0,0 +1,18 @@ +{{- if .Values.podDisruptionBudget -}} +{{ if $.Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" -}} +apiVersion: policy/v1 +{{- else -}} +apiVersion: policy/v1beta1 +{{- end }} +kind: PodDisruptionBudget +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ template "kube-state-metrics.name" . }} +{{ toYaml .Values.podDisruptionBudget | indent 2 }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..8905e113e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/podsecuritypolicy.yaml @@ -0,0 +1,39 @@ +{{- if and .Values.podSecurityPolicy.enabled (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +{{- if .Values.podSecurityPolicy.annotations }} + annotations: +{{ toYaml .Values.podSecurityPolicy.annotations | indent 4 }} +{{- end }} +spec: + privileged: false + volumes: + - 'secret' +{{- if .Values.podSecurityPolicy.additionalVolumes }} +{{ toYaml .Values.podSecurityPolicy.additionalVolumes | indent 4 }} +{{- end }} + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'MustRunAsNonRoot' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrole.yaml new file mode 100644 index 000000000..654e4a3d5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrole.yaml @@ -0,0 +1,19 @@ +{{- if and .Values.podSecurityPolicy.enabled (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: psp-{{ template "kube-state-metrics.fullname" . }} +rules: +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare "> 1.15.0-0" $kubeTargetVersion }} +- apiGroups: ['policy'] +{{- else }} +- apiGroups: ['extensions'] +{{- end }} + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "kube-state-metrics.fullname" . }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml new file mode 100644 index 000000000..5b62a18bd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/psp-clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.podSecurityPolicy.enabled (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: psp-{{ template "kube-state-metrics.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: psp-{{ template "kube-state-metrics.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "kube-state-metrics.serviceAccountName" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rbac-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rbac-configmap.yaml new file mode 100644 index 000000000..671dc9d66 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rbac-configmap.yaml @@ -0,0 +1,22 @@ +{{- if .Values.kubeRBACProxy.enabled}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "kube-state-metrics.fullname" . }}-rbac-config + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + {{- if .Values.annotations }} + annotations: + {{ toYaml .Values.annotations | nindent 4 }} + {{- end }} +data: + config-file.yaml: |+ + authorization: + resourceAttributes: + namespace: {{ template "kube-state-metrics.namespace" . }} + apiVersion: v1 + resource: services + subresource: {{ template "kube-state-metrics.fullname" . }} + name: {{ template "kube-state-metrics.fullname" . }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/role.yaml new file mode 100644 index 000000000..d33687f2d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/role.yaml @@ -0,0 +1,212 @@ +{{- if and (eq .Values.rbac.create true) (not .Values.rbac.useExistingRole) -}} +{{- range (ternary (join "," .Values.namespaces | split "," ) (list "") (eq $.Values.rbac.useClusterRole false)) }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +{{- if eq $.Values.rbac.useClusterRole false }} +kind: Role +{{- else }} +kind: ClusterRole +{{- end }} +metadata: + labels: + {{- include "kube-state-metrics.labels" $ | indent 4 }} + name: {{ template "kube-state-metrics.fullname" $ }} +{{- if eq $.Values.rbac.useClusterRole false }} + namespace: {{ . }} +{{- end }} +rules: +{{ if has "certificatesigningrequests" $.Values.collectors }} +- apiGroups: ["certificates.k8s.io"] + resources: + - certificatesigningrequests + verbs: ["list", "watch"] +{{ end -}} +{{ if has "configmaps" $.Values.collectors }} +- apiGroups: [""] + resources: + - configmaps + verbs: ["list", "watch"] +{{ end -}} +{{ if has "cronjobs" $.Values.collectors }} +- apiGroups: ["batch"] + resources: + - cronjobs + verbs: ["list", "watch"] +{{ end -}} +{{ if has "daemonsets" $.Values.collectors }} +- apiGroups: ["extensions", "apps"] + resources: + - daemonsets + verbs: ["list", "watch"] +{{ end -}} +{{ if has "deployments" $.Values.collectors }} +- apiGroups: ["extensions", "apps"] + resources: + - deployments + verbs: ["list", "watch"] +{{ end -}} +{{ if has "endpoints" $.Values.collectors }} +- apiGroups: [""] + resources: + - endpoints + verbs: ["list", "watch"] +{{ end -}} +{{ if has "endpointslices" $.Values.collectors }} +- apiGroups: ["discovery.k8s.io"] + resources: + - endpointslices + verbs: ["list", "watch"] +{{ end -}} +{{ if has "horizontalpodautoscalers" $.Values.collectors }} +- apiGroups: ["autoscaling"] + resources: + - horizontalpodautoscalers + verbs: ["list", "watch"] +{{ end -}} +{{ if has "ingresses" $.Values.collectors }} +- apiGroups: ["extensions", "networking.k8s.io"] + resources: + - ingresses + verbs: ["list", "watch"] +{{ end -}} +{{ if has "jobs" $.Values.collectors }} +- apiGroups: ["batch"] + resources: + - jobs + verbs: ["list", "watch"] +{{ end -}} +{{ if has "leases" $.Values.collectors }} +- apiGroups: ["coordination.k8s.io"] + resources: + - leases + verbs: ["list", "watch"] +{{ end -}} +{{ if has "limitranges" $.Values.collectors }} +- apiGroups: [""] + resources: + - limitranges + verbs: ["list", "watch"] +{{ end -}} +{{ if has "mutatingwebhookconfigurations" $.Values.collectors }} +- apiGroups: ["admissionregistration.k8s.io"] + resources: + - mutatingwebhookconfigurations + verbs: ["list", "watch"] +{{ end -}} +{{ if has "namespaces" $.Values.collectors }} +- apiGroups: [""] + resources: + - namespaces + verbs: ["list", "watch"] +{{ end -}} +{{ if has "networkpolicies" $.Values.collectors }} +- apiGroups: ["networking.k8s.io"] + resources: + - networkpolicies + verbs: ["list", "watch"] +{{ end -}} +{{ if has "nodes" $.Values.collectors }} +- apiGroups: [""] + resources: + - nodes + verbs: ["list", "watch"] +{{ end -}} +{{ if has "persistentvolumeclaims" $.Values.collectors }} +- apiGroups: [""] + resources: + - persistentvolumeclaims + verbs: ["list", "watch"] +{{ end -}} +{{ if has "persistentvolumes" $.Values.collectors }} +- apiGroups: [""] + resources: + - persistentvolumes + verbs: ["list", "watch"] +{{ end -}} +{{ if has "poddisruptionbudgets" $.Values.collectors }} +- apiGroups: ["policy"] + resources: + - poddisruptionbudgets + verbs: ["list", "watch"] +{{ end -}} +{{ if has "pods" $.Values.collectors }} +- apiGroups: [""] + resources: + - pods + verbs: ["list", "watch"] +{{ end -}} +{{ if has "replicasets" $.Values.collectors }} +- apiGroups: ["extensions", "apps"] + resources: + - replicasets + verbs: ["list", "watch"] +{{ end -}} +{{ if has "replicationcontrollers" $.Values.collectors }} +- apiGroups: [""] + resources: + - replicationcontrollers + verbs: ["list", "watch"] +{{ end -}} +{{ if has "resourcequotas" $.Values.collectors }} +- apiGroups: [""] + resources: + - resourcequotas + verbs: ["list", "watch"] +{{ end -}} +{{ if has "secrets" $.Values.collectors }} +- apiGroups: [""] + resources: + - secrets + verbs: ["list", "watch"] +{{ end -}} +{{ if has "services" $.Values.collectors }} +- apiGroups: [""] + resources: + - services + verbs: ["list", "watch"] +{{ end -}} +{{ if has "statefulsets" $.Values.collectors }} +- apiGroups: ["apps"] + resources: + - statefulsets + verbs: ["list", "watch"] +{{ end -}} +{{ if has "storageclasses" $.Values.collectors }} +- apiGroups: ["storage.k8s.io"] + resources: + - storageclasses + verbs: ["list", "watch"] +{{ end -}} +{{ if has "validatingwebhookconfigurations" $.Values.collectors }} +- apiGroups: ["admissionregistration.k8s.io"] + resources: + - validatingwebhookconfigurations + verbs: ["list", "watch"] +{{ end -}} +{{ if has "volumeattachments" $.Values.collectors }} +- apiGroups: ["storage.k8s.io"] + resources: + - volumeattachments + verbs: ["list", "watch"] +{{ end -}} +{{- if $.Values.kubeRBACProxy.enabled }} +- apiGroups: ["authentication.k8s.io"] + resources: + - tokenreviews + verbs: ["create"] +- apiGroups: ["authorization.k8s.io"] + resources: + - subjectaccessreviews + verbs: ["create"] +{{- end }} +{{- if $.Values.customResourceState.enabled }} +- apiGroups: ["apiextensions.k8s.io"] + resources: + - customresourcedefinitions + verbs: ["list", "watch"] +{{- end }} +{{ if $.Values.rbac.extraRules }} +{{ toYaml $.Values.rbac.extraRules }} +{{ end }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rolebinding.yaml new file mode 100644 index 000000000..330651b73 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/rolebinding.yaml @@ -0,0 +1,24 @@ +{{- if and (eq .Values.rbac.create true) (eq .Values.rbac.useClusterRole false) -}} +{{- range (join "," $.Values.namespaces) | split "," }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + {{- include "kube-state-metrics.labels" $ | indent 4 }} + name: {{ template "kube-state-metrics.fullname" $ }} + namespace: {{ . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role +{{- if (not $.Values.rbac.useExistingRole) }} + name: {{ template "kube-state-metrics.fullname" $ }} +{{- else }} + name: {{ $.Values.rbac.useExistingRole }} +{{- end }} +subjects: +- kind: ServiceAccount + name: {{ template "kube-state-metrics.serviceAccountName" $ }} + namespace: {{ template "kube-state-metrics.namespace" $ }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/service.yaml new file mode 100644 index 000000000..90c235148 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/service.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + annotations: + {{- if .Values.prometheusScrape }} + prometheus.io/scrape: '{{ .Values.prometheusScrape }}' + {{- end }} + {{- if .Values.service.annotations }} + {{- toYaml .Values.service.annotations | nindent 4 }} + {{- end }} +spec: + type: "{{ .Values.service.type }}" + {{- if .Values.service.ipDualStack.enabled }} + ipFamilies: {{ toYaml .Values.service.ipDualStack.ipFamilies | nindent 4 }} + ipFamilyPolicy: {{ .Values.service.ipDualStack.ipFamilyPolicy }} + {{- end }} + ports: + - name: "http" + protocol: TCP + port: {{ .Values.service.port | default 8080}} + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + targetPort: {{ .Values.service.port | default 8080}} + {{ if .Values.selfMonitor.enabled }} + - name: "metrics" + protocol: TCP + port: {{ .Values.selfMonitor.telemetryPort | default 8081 }} + targetPort: {{ .Values.selfMonitor.telemetryPort | default 8081 }} + {{- if .Values.selfMonitor.telemetryNodePort }} + nodePort: {{ .Values.selfMonitor.telemetryNodePort }} + {{- end }} + {{ end }} +{{- if .Values.service.loadBalancerIP }} + loadBalancerIP: "{{ .Values.service.loadBalancerIP }}" +{{- end }} +{{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} +{{- if .Values.autosharding.enabled }} + clusterIP: None +{{- else if .Values.service.clusterIP }} + clusterIP: "{{ .Values.service.clusterIP }}" +{{- end }} + selector: + {{- include "kube-state-metrics.selectorLabels" . | indent 4 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/serviceaccount.yaml new file mode 100644 index 000000000..c302bc7ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/serviceaccount.yaml @@ -0,0 +1,18 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +metadata: + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + name: {{ template "kube-state-metrics.serviceAccountName" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +{{- if .Values.serviceAccount.annotations }} + annotations: +{{ toYaml .Values.serviceAccount.annotations | indent 4 }} +{{- end }} +{{- if or .Values.serviceAccount.imagePullSecrets .Values.global.imagePullSecrets }} +imagePullSecrets: + {{- include "kube-state-metrics.imagePullSecrets" (dict "Values" .Values "imagePullSecrets" .Values.serviceAccount.imagePullSecrets) | indent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/servicemonitor.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/servicemonitor.yaml new file mode 100644 index 000000000..99d7fa924 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/servicemonitor.yaml @@ -0,0 +1,120 @@ +{{- if .Values.prometheus.monitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} + {{- with .Values.prometheus.monitor.additionalLabels }} + {{- tpl (toYaml . | nindent 4) $ }} + {{- end }} + {{- with .Values.prometheus.monitor.annotations }} + annotations: + {{- tpl (toYaml . | nindent 4) $ }} + {{- end }} +spec: + jobLabel: {{ default "app.kubernetes.io/name" .Values.prometheus.monitor.jobLabel }} + {{- with .Values.prometheus.monitor.targetLabels }} + targetLabels: + {{- toYaml . | trim | nindent 4 }} + {{- end }} + {{- with .Values.prometheus.monitor.podTargetLabels }} + podTargetLabels: + {{- toYaml . | trim | nindent 4 }} + {{- end }} + {{- include "servicemonitor.scrapeLimits" .Values.prometheus.monitor | indent 2 }} + {{- if .Values.prometheus.monitor.namespaceSelector }} + namespaceSelector: + matchNames: + {{- with .Values.prometheus.monitor.namespaceSelector }} + {{- toYaml . | nindent 6 }} + {{- end }} + {{- end }} + selector: + matchLabels: + {{- with .Values.prometheus.monitor.selectorOverride }} + {{- toYaml . | nindent 6 }} + {{- else }} + {{- include "kube-state-metrics.selectorLabels" . | indent 6 }} + {{- end }} + endpoints: + - port: http + {{- if or .Values.prometheus.monitor.http.interval .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.http.interval | default .Values.prometheus.monitor.interval }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.scrapeTimeout .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.http.scrapeTimeout | default .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.proxyUrl .Values.prometheus.monitor.proxyUrl }} + proxyUrl: {{ .Values.prometheus.monitor.http.proxyUrl | default .Values.prometheus.monitor.proxyUrl }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.enableHttp2 .Values.prometheus.monitor.enableHttp2 }} + enableHttp2: {{ .Values.prometheus.monitor.http.enableHttp2 | default .Values.prometheus.monitor.enableHttp2 }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.honorLabels .Values.prometheus.monitor.honorLabels }} + honorLabels: true + {{- end }} + {{- if or .Values.prometheus.monitor.http.metricRelabelings .Values.prometheus.monitor.metricRelabelings }} + metricRelabelings: + {{- toYaml (.Values.prometheus.monitor.http.metricRelabelings | default .Values.prometheus.monitor.metricRelabelings) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.relabelings .Values.prometheus.monitor.relabelings }} + relabelings: + {{- toYaml (.Values.prometheus.monitor.http.relabelings | default .Values.prometheus.monitor.relabelings) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.scheme .Values.prometheus.monitor.scheme }} + scheme: {{ .Values.prometheus.monitor.http.scheme | default .Values.prometheus.monitor.scheme }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.tlsConfig .Values.prometheus.monitor.tlsConfig }} + tlsConfig: + {{- toYaml (.Values.prometheus.monitor.http.tlsConfig | default .Values.prometheus.monitor.tlsConfig) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.http.bearerTokenFile .Values.prometheus.monitor.bearerTokenFile }} + bearerTokenFile: {{ .Values.prometheus.monitor.http.bearerTokenFile | default .Values.prometheus.monitor.bearerTokenFile }} + {{- end }} + {{- with (.Values.prometheus.monitor.http.bearerTokenSecret | default .Values.prometheus.monitor.bearerTokenSecret) }} + bearerTokenSecret: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.selfMonitor.enabled }} + - port: metrics + {{- if or .Values.prometheus.monitor.metrics.interval .Values.prometheus.monitor.interval }} + interval: {{ .Values.prometheus.monitor.metrics.interval | default .Values.prometheus.monitor.interval }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.scrapeTimeout .Values.prometheus.monitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.monitor.metrics.scrapeTimeout | default .Values.prometheus.monitor.scrapeTimeout }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.proxyUrl .Values.prometheus.monitor.proxyUrl }} + proxyUrl: {{ .Values.prometheus.monitor.metrics.proxyUrl | default .Values.prometheus.monitor.proxyUrl }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.enableHttp2 .Values.prometheus.monitor.enableHttp2 }} + enableHttp2: {{ .Values.prometheus.monitor.metrics.enableHttp2 | default .Values.prometheus.monitor.enableHttp2 }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.honorLabels .Values.prometheus.monitor.honorLabels }} + honorLabels: true + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.metricRelabelings .Values.prometheus.monitor.metricRelabelings }} + metricRelabelings: + {{- toYaml (.Values.prometheus.monitor.metrics.metricRelabelings | default .Values.prometheus.monitor.metricRelabelings) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.relabelings .Values.prometheus.monitor.relabelings }} + relabelings: + {{- toYaml (.Values.prometheus.monitor.metrics.relabelings | default .Values.prometheus.monitor.relabelings) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.scheme .Values.prometheus.monitor.scheme }} + scheme: {{ .Values.prometheus.monitor.metrics.scheme | default .Values.prometheus.monitor.scheme }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.tlsConfig .Values.prometheus.monitor.tlsConfig }} + tlsConfig: + {{- toYaml (.Values.prometheus.monitor.metrics.tlsConfig | default .Values.prometheus.monitor.tlsConfig) | nindent 8 }} + {{- end }} + {{- if or .Values.prometheus.monitor.metrics.bearerTokenFile .Values.prometheus.monitor.bearerTokenFile }} + bearerTokenFile: {{ .Values.prometheus.monitor.metrics.bearerTokenFile | default .Values.prometheus.monitor.bearerTokenFile }} + {{- end }} + {{- with (.Values.prometheus.monitor.metrics.bearerTokenSecret | default .Values.prometheus.monitor.bearerTokenSecret) }} + bearerTokenSecret: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-role.yaml new file mode 100644 index 000000000..489de147c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-role.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.autosharding.enabled .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: stsdiscovery-{{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - get +- apiGroups: + - apps + resourceNames: + - {{ template "kube-state-metrics.fullname" . }} + resources: + - statefulsets + verbs: + - get + - list + - watch +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml new file mode 100644 index 000000000..73b37a4f6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/stsdiscovery-rolebinding.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.autosharding.enabled .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: stsdiscovery-{{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: stsdiscovery-{{ template "kube-state-metrics.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "kube-state-metrics.serviceAccountName" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml new file mode 100644 index 000000000..f46305b51 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/templates/verticalpodautoscaler.yaml @@ -0,0 +1,44 @@ +{{- if and (.Capabilities.APIVersions.Has "autoscaling.k8s.io/v1") (.Values.verticalPodAutoscaler.enabled) }} +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: {{ template "kube-state-metrics.fullname" . }} + namespace: {{ template "kube-state-metrics.namespace" . }} + labels: + {{- include "kube-state-metrics.labels" . | indent 4 }} +spec: + {{- with .Values.verticalPodAutoscaler.recommenders }} + recommenders: + {{- toYaml . | nindent 4 }} + {{- end }} + resourcePolicy: + containerPolicies: + - containerName: {{ template "kube-state-metrics.name" . }} + {{- with .Values.verticalPodAutoscaler.controlledResources }} + controlledResources: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.verticalPodAutoscaler.controlledValues }} + controlledValues: {{ .Values.verticalPodAutoscaler.controlledValues }} + {{- end }} + {{- if .Values.verticalPodAutoscaler.maxAllowed }} + maxAllowed: + {{ toYaml .Values.verticalPodAutoscaler.maxAllowed | nindent 8 }} + {{- end }} + {{- if .Values.verticalPodAutoscaler.minAllowed }} + minAllowed: + {{ toYaml .Values.verticalPodAutoscaler.minAllowed | nindent 8 }} + {{- end }} + targetRef: + apiVersion: apps/v1 + {{- if .Values.autosharding.enabled }} + kind: StatefulSet + {{- else }} + kind: Deployment + {{- end }} + name: {{ template "kube-state-metrics.fullname" . }} + {{- with .Values.verticalPodAutoscaler.updatePolicy }} + updatePolicy: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/values.yaml new file mode 100644 index 000000000..a7b2bdad6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/kube-state-metrics/values.yaml @@ -0,0 +1,542 @@ +# Default values for kube-state-metrics. +prometheusScrape: true +image: + registry: registry.k8s.io + repository: kube-state-metrics/kube-state-metrics + # If unset use v + .Charts.appVersion + tag: "" + sha: "" + pullPolicy: IfNotPresent + +imagePullSecrets: [] +# - name: "image-pull-secret" + +global: + # To help compatibility with other charts which use global.imagePullSecrets. + # Allow either an array of {name: pullSecret} maps (k8s-style), or an array of strings (more common helm-style). + # global: + # imagePullSecrets: + # - name: pullSecret1 + # - name: pullSecret2 + # or + # global: + # imagePullSecrets: + # - pullSecret1 + # - pullSecret2 + imagePullSecrets: [] + # + # Allow parent charts to override registry hostname + imageRegistry: "" + +# If set to true, this will deploy kube-state-metrics as a StatefulSet and the data +# will be automatically sharded across <.Values.replicas> pods using the built-in +# autodiscovery feature: https://github.com/kubernetes/kube-state-metrics#automated-sharding +# This is an experimental feature and there are no stability guarantees. +autosharding: + enabled: false + +replicas: 1 + +# Change the deployment strategy when autosharding is disabled. +# ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy +# The default is "RollingUpdate" as per Kubernetes defaults. +# During a release, 'RollingUpdate' can lead to two running instances for a short period of time while 'Recreate' can create a small gap in data. +# updateStrategy: Recreate + +# Number of old history to retain to allow rollback +# Default Kubernetes value is set to 10 +revisionHistoryLimit: 10 + +# List of additional cli arguments to configure kube-state-metrics +# for example: --enable-gzip-encoding, --log-file, etc. +# all the possible args can be found here: https://github.com/kubernetes/kube-state-metrics/blob/master/docs/cli-arguments.md +extraArgs: [] + +# If false then the user will opt out of automounting API credentials. +automountServiceAccountToken: true + +service: + port: 8080 + # Default to clusterIP for backward compatibility + type: ClusterIP + ipDualStack: + enabled: false + ipFamilies: ["IPv6", "IPv4"] + ipFamilyPolicy: "PreferDualStack" + nodePort: 0 + loadBalancerIP: "" + # Only allow access to the loadBalancerIP from these IPs + loadBalancerSourceRanges: [] + clusterIP: "" + annotations: {} + +## Additional labels to add to all resources +customLabels: {} + # app: kube-state-metrics + +## Override selector labels +selectorOverride: {} + +## set to true to add the release label so scraping of the servicemonitor with kube-prometheus-stack works out of the box +releaseLabel: false + +hostNetwork: false + +rbac: + # If true, create & use RBAC resources + create: true + + # Set to a rolename to use existing role - skipping role creating - but still doing serviceaccount and rolebinding to it, rolename set here. + # useExistingRole: your-existing-role + + # If set to false - Run without Cluteradmin privs needed - ONLY works if namespace is also set (if useExistingRole is set this name is used as ClusterRole or Role to bind to) + useClusterRole: true + + # Add permissions for CustomResources' apiGroups in Role/ClusterRole. Should be used in conjunction with Custom Resource State Metrics configuration + # Example: + # - apiGroups: ["monitoring.coreos.com"] + # resources: ["prometheuses"] + # verbs: ["list", "watch"] + extraRules: [] + +# Configure kube-rbac-proxy. When enabled, creates one kube-rbac-proxy container per exposed HTTP endpoint (metrics and telemetry if enabled). +# The requests are served through the same service but requests are then HTTPS. +kubeRBACProxy: + enabled: false + image: + registry: quay.io + repository: brancz/kube-rbac-proxy + tag: v0.18.0 + sha: "" + pullPolicy: IfNotPresent + + # List of additional cli arguments to configure kube-rbac-prxy + # for example: --tls-cipher-suites, --log-file, etc. + # all the possible args can be found here: https://github.com/brancz/kube-rbac-proxy#usage + extraArgs: [] + + ## Specify security settings for a Container + ## Allows overrides and additional options compared to (Pod) securityContext + ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + containerSecurityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + + 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: 64Mi + # requests: + # cpu: 10m + # memory: 32Mi + + ## volumeMounts enables mounting custom volumes in rbac-proxy containers + ## Useful for TLS certificates and keys + volumeMounts: [] + # - mountPath: /etc/tls + # name: kube-rbac-proxy-tls + # readOnly: true + +serviceAccount: + # Specifies whether a ServiceAccount should be created, require rbac true + create: true + # The name of the ServiceAccount to use. + # If not set and create is true, a name is generated using the fullname template + name: + # Reference to one or more secrets to be used when pulling images + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + imagePullSecrets: [] + # ServiceAccount annotations. + # Use case: AWS EKS IAM roles for service accounts + # ref: https://docs.aws.amazon.com/eks/latest/userguide/specify-service-account-role.html + annotations: {} + # If false then the user will opt out of automounting API credentials. + automountServiceAccountToken: true + +prometheus: + monitor: + enabled: false + annotations: {} + additionalLabels: {} + namespace: "" + namespaceSelector: [] + jobLabel: "" + targetLabels: [] + podTargetLabels: [] + ## SampleLimit defines per-scrape limit on number of scraped samples that will be accepted. + ## + sampleLimit: 0 + + ## TargetLimit defines a limit on the number of scraped targets that will be accepted. + ## + targetLimit: 0 + + ## Per-scrape limit on number of labels that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. + ## + labelLimit: 0 + + ## Per-scrape limit on length of labels name that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. + ## + labelNameLengthLimit: 0 + + ## Per-scrape limit on length of labels value that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. + ## + labelValueLengthLimit: 0 + selectorOverride: {} + + ## kube-state-metrics endpoint + http: + interval: "" + scrapeTimeout: "" + proxyUrl: "" + ## Whether to enable HTTP2 for servicemonitor + enableHttp2: false + honorLabels: false + metricRelabelings: [] + relabelings: [] + scheme: "" + ## File to read bearer token for scraping targets + bearerTokenFile: "" + ## Secret to mount to read bearer token for scraping targets. The secret needs + ## to be in the same namespace as the service monitor and accessible by the + ## Prometheus Operator + bearerTokenSecret: {} + # name: secret-name + # key: key-name + tlsConfig: {} + + ## selfMonitor endpoint + metrics: + interval: "" + scrapeTimeout: "" + proxyUrl: "" + ## Whether to enable HTTP2 for servicemonitor + enableHttp2: false + honorLabels: false + metricRelabelings: [] + relabelings: [] + scheme: "" + ## File to read bearer token for scraping targets + bearerTokenFile: "" + ## Secret to mount to read bearer token for scraping targets. The secret needs + ## to be in the same namespace as the service monitor and accessible by the + ## Prometheus Operator + bearerTokenSecret: {} + # name: secret-name + # key: key-name + tlsConfig: {} + +## Specify if a Pod Security Policy for kube-state-metrics must be created +## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +podSecurityPolicy: + enabled: false + annotations: {} + ## Specify pod annotations + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#apparmor + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#sysctl + ## + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default' + # apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + + additionalVolumes: [] + +## Configure network policy for kube-state-metrics +networkPolicy: + enabled: false + # networkPolicy.flavor -- Flavor of the network policy to use. + # Can be: + # * kubernetes for networking.k8s.io/v1/NetworkPolicy + # * cilium for cilium.io/v2/CiliumNetworkPolicy + flavor: kubernetes + + ## Configure the cilium network policy kube-apiserver selector + # cilium: + # kubeApiServerSelector: + # - toEntities: + # - kube-apiserver + + # egress: + # - {} + # ingress: + # - {} + # podSelector: + # matchLabels: + # app.kubernetes.io/name: kube-state-metrics + +securityContext: + enabled: true + runAsGroup: 65534 + runAsUser: 65534 + fsGroup: 65534 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + +## Specify security settings for a Container +## Allows overrides and additional options compared to (Pod) securityContext +## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +containerSecurityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + +## Node labels for pod assignment +## Ref: https://kubernetes.io/docs/user-guide/node-selection/ +nodeSelector: {} + +## Affinity settings for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +affinity: {} + +## Tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + +## Topology spread constraints for pod assignment +## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ +topologySpreadConstraints: [] + +# Annotations to be added to the deployment/statefulset +annotations: {} + +# Annotations to be added to the pod +podAnnotations: {} + +# Labels to be added to the pod +podLabels: {} + +## Assign a PriorityClassName to pods if set +# priorityClassName: "" + +# Ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +podDisruptionBudget: {} + +# Comma-separated list of metrics to be exposed. +# This list comprises of exact metric names and/or regex patterns. +# The allowlist and denylist are mutually exclusive. +metricAllowlist: [] + +# Comma-separated list of metrics not to be enabled. +# This list comprises of exact metric names and/or regex patterns. +# The allowlist and denylist are mutually exclusive. +metricDenylist: [] + +# Comma-separated list of additional Kubernetes label keys that will be used in the resource's +# labels metric. By default the metric contains only name and namespace labels. +# To include additional labels, provide a list of resource names in their plural form and Kubernetes +# label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. +# A single '*' can be provided per resource instead to allow any labels, but that has +# severe performance implications (Example: '=pods=[*]'). +metricLabelsAllowlist: [] + # - namespaces=[k8s-label-1,k8s-label-n] + +# Comma-separated list of Kubernetes annotations keys that will be used in the resource' +# labels metric. By default the metric contains only name and namespace labels. +# To include additional annotations provide a list of resource names in their plural form and Kubernetes +# annotation keys you would like to allow for them (Example: '=namespaces=[kubernetes.io/team,...],pods=[kubernetes.io/team],...)'. +# A single '*' can be provided per resource instead to allow any annotations, but that has +# severe performance implications (Example: '=pods=[*]'). +metricAnnotationsAllowList: [] + # - pods=[k8s-annotation-1,k8s-annotation-n] + +# Available collectors for kube-state-metrics. +# By default, all available resources are enabled, comment out to disable. +collectors: + - certificatesigningrequests + - configmaps + - cronjobs + - daemonsets + - deployments + - endpoints + - horizontalpodautoscalers + - ingresses + - jobs + - leases + - limitranges + - mutatingwebhookconfigurations + - namespaces + - networkpolicies + - nodes + - persistentvolumeclaims + - persistentvolumes + - poddisruptionbudgets + - pods + - replicasets + - replicationcontrollers + - resourcequotas + - secrets + - services + - statefulsets + - storageclasses + - validatingwebhookconfigurations + - volumeattachments + +# Enabling kubeconfig will pass the --kubeconfig argument to the container +kubeconfig: + enabled: false + # base64 encoded kube-config file + secret: + +# Enabling support for customResourceState, will create a configMap including your config that will be read from kube-state-metrics +customResourceState: + enabled: false + # Add (Cluster)Role permissions to list/watch the customResources defined in the config to rbac.extraRules + config: {} + +# Enable only the release namespace for collecting resources. By default all namespaces are collected. +# If releaseNamespace and namespaces are both set a merged list will be collected. +releaseNamespace: false + +# Comma-separated list(string) or yaml list of namespaces to be enabled for collecting resources. By default all namespaces are collected. +namespaces: "" + +# Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, +# only namespaces that are excluded in namespaces-denylist will be used. +namespacesDenylist: "" + +## Override the deployment namespace +## +namespaceOverride: "" + +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: 64Mi + # requests: + # cpu: 10m + # memory: 32Mi + +## Provide a k8s version to define apiGroups for podSecurityPolicy Cluster Role. +## For example: kubeTargetVersionOverride: 1.14.9 +## +kubeTargetVersionOverride: "" + +# Enable self metrics configuration for service and Service Monitor +# Default values for telemetry configuration can be overridden +# If you set telemetryNodePort, you must also set service.type to NodePort +selfMonitor: + enabled: false + # telemetryHost: 0.0.0.0 + # telemetryPort: 8081 + # telemetryNodePort: 0 + +# Enable vertical pod autoscaler support for kube-state-metrics +verticalPodAutoscaler: + enabled: false + + # Recommender responsible for generating recommendation for the object. + # List should be empty (then the default recommender will generate the recommendation) + # or contain exactly one recommender. + # recommenders: [] + # - name: custom-recommender-performance + + # List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory + controlledResources: [] + # Specifies which resource values should be controlled: RequestsOnly or RequestsAndLimits. + # controlledValues: RequestsAndLimits + + # Define the max allowed resources for the pod + maxAllowed: {} + # cpu: 200m + # memory: 100Mi + # Define the min allowed resources for the pod + minAllowed: {} + # cpu: 200m + # memory: 100Mi + + # updatePolicy: + # Specifies minimal number of replicas which need to be alive for VPA Updater to attempt pod eviction + # minReplicas: 1 + # Specifies whether recommended updates are applied when a Pod is started and whether recommended updates + # are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto". + # updateMode: Auto + +# volumeMounts are used to add custom volume mounts to deployment. +# See example below +volumeMounts: [] +# - mountPath: /etc/config +# name: config-volume + +# volumes are used to add custom volumes to deployment +# See example below +volumes: [] +# - configMap: +# name: cm-for-volume +# name: config-volume + +# Extra manifests to deploy as an array +extraManifests: [] + # - apiVersion: v1 + # kind: ConfigMap + # metadata: + # labels: + # name: prometheus-extra + # data: + # extra-data: "value" + +## Containers allows injecting additional containers. +containers: [] + # - name: crd-init + # image: kiwigrid/k8s-sidecar:latest + +## InitContainers allows injecting additional initContainers. +initContainers: [] + # - name: crd-sidecar + # image: kiwigrid/k8s-sidecar:latest + +## Settings for startup, liveness and readiness probes +## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +## + +## Startup probe can optionally be enabled. +## +startupProbe: + enabled: false + failureThreshold: 3 + httpGet: + httpHeaders: [] + scheme: http + initialDelaySeconds: 0 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + +## Liveness probe +## +livenessProbe: + failureThreshold: 3 + httpGet: + httpHeaders: [] + scheme: http + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + +## Readiness probe +## +readinessProbe: + failureThreshold: 3 + httpGet: + httpHeaders: [] + scheme: http + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/.helmignore new file mode 100644 index 000000000..f62b5519e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/.helmignore @@ -0,0 +1 @@ +templates/admission-webhooks/job-patch/README.md diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.lock new file mode 100644 index 000000000..9efaa36a6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T22:48:07.029709954Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.yaml new file mode 100644 index 000000000..9282a9f3b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/Chart.yaml @@ -0,0 +1,35 @@ +apiVersion: v2 +appVersion: 0.20.0 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic Infrastructure Kubernetes Operator. +home: https://hub.docker.com/r/newrelic/newrelic-infra-operator +icon: https://newrelic.com/themes/custom/curio/assets/mediakit/new_relic_logo_vertical.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: alvarocabanas + url: https://github.com/alvarocabanas +- name: carlossscastro + url: https://github.com/carlossscastro +- name: sigilioso + url: https://github.com/sigilioso +- name: gsanchezgavier + url: https://github.com/gsanchezgavier +- name: kang-makes + url: https://github.com/kang-makes +- name: marcsanmi + url: https://github.com/marcsanmi +- name: paologallinaharbur + url: https://github.com/paologallinaharbur +- name: roobre + url: https://github.com/roobre +name: newrelic-infra-operator +sources: +- https://github.com/newrelic/newrelic-infra-operator +- https://github.com/newrelic/newrelic-infra-operator/tree/main/charts/newrelic-infra-operator +version: 2.12.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md new file mode 100644 index 000000000..05e8a8d48 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md @@ -0,0 +1,114 @@ +# newrelic-infra-operator + +A Helm chart to deploy the New Relic Infrastructure Kubernetes Operator. + +**Homepage:** + +## Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add newrelic-infra-operator https://newrelic.github.io/newrelic-infra-operator +helm upgrade --install newrelic-infra-operator/newrelic-infra-operator -f your-custom-values.yaml +``` + +## Source Code + +* +* + +## Usage example + +Make sure you have [added the New Relic chart repository.](../../README.md#install) + +Then, to install this chart, run the following command: + +```sh +helm upgrade --install [release-name] newrelic-infra-operator/newrelic-infra-operator --set cluster=my_cluster_name --set licenseKey [your-license-key] +``` + +When installing on Fargate add as well `--set fargate=true` + +### Configure in which pods the sidecar should be injected + +Policies are available in order to configure in which pods the sidecar should be injected. +Each policy is evaluated independently and if at least one policy matches the operator will inject the sidecar. + +Policies are composed by `namespaceSelector` checking the labels of the Pod namespace, `podSelector` checking +the labels of the Pod and `namespace` checking the namespace name. Each of those, if specified, are ANDed. + +By default, the policies are configured in order to inject the sidecar in each pod belonging to a Fargate profile. + +> Moreover, it is possible to add the label `infra-operator.newrelic.com/disable-injection` to Pods to exclude injection +for a single Pod that otherwise would be selected by the policies. + +Please make sure to configure policies correctly to avoid injecting sidecar for pods running on EC2 nodes +already monitored by the infrastructure DaemonSet. + +### Configure the sidecar with labelsSelectors + +It is also possible to configure `resourceRequirements` and `extraEnvVars` based on the labels of the mutating Pod. + +The current configuration increases the resource requirements for sidecar injected on `KSM` instances. Moreover, +injectes disable the `DISABLE_KUBE_STATE_METRICS` environment variable for Pods not running on `KSM` instances +to decrease the load on the API server. + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| admissionWebhooksPatchJob | object | See `values.yaml` | Image used to create certificates and inject them to the admission webhook | +| admissionWebhooksPatchJob.image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| admissionWebhooksPatchJob.volumeMounts | list | `[]` | Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies. Enforce a read-only root. | +| admissionWebhooksPatchJob.volumes | list | `[]` | Volumes to add to the job container. | +| affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` | +| certManager.enabled | bool | `false` | Use cert manager for webhook certs | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Mandatory. Can be configured also with `global.cluster` | +| config | object | See `values.yaml` | Operator configuration | +| config.ignoreMutationErrors | bool | `true` | IgnoreMutationErrors instruments the operator to ignore injection error instead of failing. If set to false errors of the injection could block the creation of pods. | +| config.infraAgentInjection | object | See `values.yaml` | configuration of the sidecar injection webhook | +| config.infraAgentInjection.agentConfig | object | See `values.yaml` | agentConfig contains the configuration for the container agent injected | +| config.infraAgentInjection.agentConfig.configSelectors | list | See `values.yaml` | configSelectors is the way to configure resource requirements and extra envVars of the injected sidecar container. When mutating it will be applied the first configuration having the labelSelector matching with the mutating pod. | +| config.infraAgentInjection.agentConfig.image | object | See `values.yaml` | Image of the infrastructure agent to be injected. | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| customSecretLicenseKey | string | `""` | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` | +| customSecretName | string | `""` | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | `false` | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| image | object | See `values.yaml` | Image for the New Relic Infrastructure Operator | +| image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` | +| podAnnotations | object | `{}` | Annotations to add to the pod. | +| podSecurityContext | object | `{"fsGroup":1001,"runAsGroup":1001,"runAsUser":1001}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | +| replicas | int | `1` | | +| resources | object | `{"limits":{"memory":"80M"},"requests":{"cpu":"100m","memory":"30M"}}` | Resources available for this pod | +| serviceAccount | object | See `values.yaml` | Settings controlling ServiceAccount creation | +| serviceAccount.create | bool | `true` | Specifies whether a ServiceAccount should be created | +| timeoutSeconds | int | `10` | Webhook timeout Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts | +| tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` | + +## Maintainers + +* [alvarocabanas](https://github.com/alvarocabanas) +* [carlossscastro](https://github.com/carlossscastro) +* [sigilioso](https://github.com/sigilioso) +* [gsanchezgavier](https://github.com/gsanchezgavier) +* [kang-makes](https://github.com/kang-makes) +* [marcsanmi](https://github.com/marcsanmi) +* [paologallinaharbur](https://github.com/paologallinaharbur) +* [roobre](https://github.com/roobre) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md.gotmpl new file mode 100644 index 000000000..1ef603355 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/README.md.gotmpl @@ -0,0 +1,77 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +## Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add newrelic-infra-operator https://newrelic.github.io/newrelic-infra-operator +helm upgrade --install newrelic-infra-operator/newrelic-infra-operator -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Usage example + +Make sure you have [added the New Relic chart repository.](../../README.md#install) + +Then, to install this chart, run the following command: + +```sh +helm upgrade --install [release-name] newrelic-infra-operator/newrelic-infra-operator --set cluster=my_cluster_name --set licenseKey [your-license-key] +``` + +When installing on Fargate add as well `--set fargate=true` + +### Configure in which pods the sidecar should be injected + +Policies are available in order to configure in which pods the sidecar should be injected. +Each policy is evaluated independently and if at least one policy matches the operator will inject the sidecar. + +Policies are composed by `namespaceSelector` checking the labels of the Pod namespace, `podSelector` checking +the labels of the Pod and `namespace` checking the namespace name. Each of those, if specified, are ANDed. + +By default, the policies are configured in order to inject the sidecar in each pod belonging to a Fargate profile. + +> Moreover, it is possible to add the label `infra-operator.newrelic.com/disable-injection` to Pods to exclude injection +for a single Pod that otherwise would be selected by the policies. + +Please make sure to configure policies correctly to avoid injecting sidecar for pods running on EC2 nodes +already monitored by the infrastructure DaemonSet. + +### Configure the sidecar with labelsSelectors + +It is also possible to configure `resourceRequirements` and `extraEnvVars` based on the labels of the mutating Pod. + +The current configuration increases the resource requirements for sidecar injected on `KSM` instances. Moreover, +injectes disable the `DISABLE_KUBE_STATE_METRICS` environment variable for Pods not running on `KSM` instances +to decrease the load on the API server. + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/ci/test-values.yaml new file mode 100644 index 000000000..3e154e1d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/ci/test-values.yaml @@ -0,0 +1,39 @@ +cluster: test-cluster +licenseKey: pleasePassCIThanks +serviceAccount: + name: newrelic-infra-operator-test +image: + repository: e2e/newrelic-infra-operator + tag: test # Defaults to AppVersion + pullPolicy: IfNotPresent + pullSecrets: + - name: test-pull-secret +admissionWebhooksPatchJob: + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: +podAnnotations: + test-annotation: test-value +affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: topology.kubernetes.io/zone + labelSelector: + matchExpressions: + - key: test-key + operator: In + values: + - test-value +tolerations: +- key: "key1" + operator: "Exists" + effect: "NoSchedule" +nodeSelector: + beta.kubernetes.io/os: linux + +fargate: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/NOTES.txt new file mode 100644 index 000000000..5b11d2d83 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/NOTES.txt @@ -0,0 +1,4 @@ +Your deployment of the New Relic Infrastructure Operator is complete. +You can check on the progress of this by running the following command: + + kubectl get deployments -o wide -w --namespace {{ .Release.Namespace }} {{ include "newrelic.common.naming.fullname" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/_helpers.tpl new file mode 100644 index 000000000..8a8858c82 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/_helpers.tpl @@ -0,0 +1,136 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} + +{{- /* +Naming helpers +*/ -}} +{{- define "newrelic-infra-operator.name.admission" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.admission" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.admission.serviceAccount" -}} +{{- if include "newrelic.common.serviceAccount.create" . -}} + {{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") -}} +{{- else -}} + {{- include "newrelic.common.serviceAccount.name" . -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-infra-operator.name.admission-create" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-create") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.admission-create" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-create") }} +{{- end -}} + +{{- define "newrelic-infra-operator.name.admission-patch" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-patch") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.admission-patch" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-patch") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.self-signed-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "self-signed-issuer") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.root-cert" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "root-cert") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.root-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "root-issuer") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.webhook-cert" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "webhook-cert") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.infra-agent" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "infra-agent") }} +{{- end -}} + +{{- define "newrelic-infra-operator.fullname.config" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "config") }} +{{- end -}} + +{{/* +Returns Infra-agent rules +*/}} +{{- define "newrelic-infra-operator.infra-agent-monitoring-rules" -}} +- apiGroups: [""] + resources: + - "nodes" + - "nodes/metrics" + - "nodes/stats" + - "nodes/proxy" + - "pods" + - "services" + - "namespaces" + verbs: ["get", "list"] +- nonResourceURLs: ["/metrics"] + verbs: ["get"] +{{- end -}} + +{{/* +Returns fargate +*/}} +{{- define "newrelic-infra-operator.fargate" -}} +{{- if .Values.global }} + {{- if .Values.global.fargate }} + {{- .Values.global.fargate -}} + {{- end -}} +{{- else if .Values.fargate }} + {{- .Values.fargate -}} +{{- end -}} +{{- end -}} + +{{/* +Returns fargate configuration for configmap data +*/}} +{{- define "newrelic-infra-operator.fargate-config" -}} +infraAgentInjection: + resourcePrefix: {{ include "newrelic.common.naming.fullname" . }} +{{- if include "newrelic-infra-operator.fargate" . }} +{{- if not .Values.config.infraAgentInjection.policies }} + policies: + - podSelector: + matchExpressions: + - key: "eks.amazonaws.com/fargate-profile" + operator: Exists +{{- end }} + agentConfig: +{{- if not .Values.config.infraAgentInjection.agentConfig.customAttributes }} + customAttributes: + - name: computeType + defaultValue: serverless + - name: fargateProfile + fromLabel: eks.amazonaws.com/fargate-profile +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Returns configmap data +*/}} +{{- define "newrelic-infra-operator.configmap.data" -}} +{{ toYaml (merge (include "newrelic-infra-operator.fargate-config" . | fromYaml) .Values.config) }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml new file mode 100644 index 000000000..44c2b3eba --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrole.yaml @@ -0,0 +1,27 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic-infra-operator.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - get + - update + {{- if .Values.rbac.pspEnabled }} + - apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ include "newrelic-infra-operator.fullname.admission" . }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml new file mode 100644 index 000000000..902206c22 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic-infra-operator.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic-infra-operator.fullname.admission" . }} +subjects: + - kind: ServiceAccount + name: {{ include "newrelic-infra-operator.fullname.admission.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml new file mode 100644 index 000000000..022e6254e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-createSecret.yaml @@ -0,0 +1,57 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.admission-create" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission-create" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "newrelic-infra-operator.fullname.admission-create" . }} + labels: + app: {{ include "newrelic-infra-operator.name.admission-create" . }} + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.admissionWebhooksPatchJob.image.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: create + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.admissionWebhooksPatchJob.image "context" .) }} + imagePullPolicy: {{ .Values.admissionWebhooksPatchJob.image.pullPolicy }} + args: + - create + - --host={{ include "newrelic.common.naming.fullname" . }},{{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "newrelic-infra-operator.fullname.admission" . }} + - --cert-name=tls.crt + - --key-name=tls.key + {{- if .Values.admissionWebhooksPatchJob.image.volumeMounts }} + volumeMounts: + {{- include "tplvalues.render" ( dict "value" .Values.admissionWebhooksPatchJob.image.volumeMounts "context" $ ) | nindent 10 }} + {{- end }} + {{- if .Values.admissionWebhooksPatchJob.image.volumes }} + volumes: + {{- include "tplvalues.render" ( dict "value" .Values.admissionWebhooksPatchJob.image.volumes "context" $ ) | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "newrelic-infra-operator.fullname.admission.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 -}} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml new file mode 100644 index 000000000..61e363678 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/job-patchWebhook.yaml @@ -0,0 +1,57 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.admission-patch" . }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission-patch" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "newrelic-infra-operator.fullname.admission-patch" . }} + labels: + app: {{ include "newrelic-infra-operator.name.admission-patch" . }} + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.admissionWebhooksPatchJob.image.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: patch + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.admissionWebhooksPatchJob.image "context" .) }} + imagePullPolicy: {{ .Values.admissionWebhooksPatchJob.image.pullPolicy }} + args: + - patch + - --webhook-name={{ include "newrelic.common.naming.fullname" . }} + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "newrelic-infra-operator.fullname.admission" . }} + - --patch-failure-policy=Ignore + - --patch-validating=false + {{- if .Values.admissionWebhooksPatchJob.image.volumeMounts }} + volumeMounts: + {{- include "tplvalues.render" ( dict "value" .Values.admissionWebhooksPatchJob.image.volumeMounts "context" $ ) | nindent 10 }} + {{- end }} + {{- if .Values.admissionWebhooksPatchJob.image.volumes }} + volumes: + {{- include "tplvalues.render" ( dict "value" .Values.admissionWebhooksPatchJob.image.volumes "context" $ ) | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "newrelic-infra-operator.fullname.admission.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 -}} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml new file mode 100644 index 000000000..64237abb4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/psp.yaml @@ -0,0 +1,50 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled) (.Values.rbac.pspEnabled)) }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "newrelic-infra-operator.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + privileged: false + # Required to prevent escalations to root. + # allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + # requiredDropCapabilities: + # - ALL + # Allow core volume types. + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + - 'persistentVolumeClaim' + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + hostIPC: false + hostPID: false + runAsUser: + # Permits the container to run with root privileges as well. + rule: 'RunAsAny' + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml new file mode 100644 index 000000000..e3213f7c5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/role.yaml @@ -0,0 +1,21 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml new file mode 100644 index 000000000..67eb79298 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "newrelic-infra-operator.fullname.admission" . }} +subjects: + - kind: ServiceAccount + name: {{ include "newrelic-infra-operator.fullname.admission.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml new file mode 100644 index 000000000..18eb7347d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/job-patch/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- $createServiceAccount := include "newrelic.common.serviceAccount.create" . -}} +{{- if (and $createServiceAccount (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.admission.serviceAccount" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic-infra-operator.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml new file mode 100644 index 000000000..efa605255 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/admission-webhooks/mutatingWebhookConfiguration.yaml @@ -0,0 +1,32 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} +{{- if .Values.certManager.enabled }} + annotations: + certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} + cert-manager.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} +{{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +webhooks: +- name: newrelic-infra-operator.newrelic.com + clientConfig: + service: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + path: "/mutate-v1-pod" +{{- if not .Values.certManager.enabled }} + caBundle: "" +{{- end }} + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Ignore + timeoutSeconds: {{ .Values.timeoutSeconds }} + sideEffects: NoneOnDryRun + admissionReviewVersions: + - v1 + reinvocationPolicy: IfNeeded diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/cert-manager.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/cert-manager.yaml new file mode 100644 index 000000000..800dc2453 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/cert-manager.yaml @@ -0,0 +1,52 @@ +{{ if .Values.certManager.enabled }} +--- +# Create a selfsigned Issuer, in order to create a root CA certificate for +# signing webhook serving certificates +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.self-signed-issuer" . }} +spec: + selfSigned: {} +--- +# Generate a CA Certificate used to sign certificates for the webhook +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.root-cert" . }} +spec: + secretName: {{ include "newrelic-infra-operator.fullname.root-cert" . }} + duration: 43800h # 5y + issuerRef: + name: {{ include "newrelic-infra-operator.fullname.self-signed-issuer" . }} + commonName: "ca.webhook.nri" + isCA: true +--- +# Create an Issuer that uses the above generated CA certificate to issue certs +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.root-issuer" . }} +spec: + ca: + secretName: {{ include "newrelic-infra-operator.fullname.root-cert" . }} +--- +# Finally, generate a serving certificate for the webhook to use +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.webhook-cert" . }} +spec: + secretName: {{ include "newrelic-infra-operator.fullname.admission" . }} + duration: 8760h # 1y + issuerRef: + name: {{ include "newrelic-infra-operator.fullname.root-issuer" . }} + dnsNames: + - {{ include "newrelic.common.naming.fullname" . }} + - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }} + - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc +{{ end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrole.yaml new file mode 100644 index 000000000..cb20e310d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrole.yaml @@ -0,0 +1,39 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + {{/* Allow creating and updating secrets with license key for infra agent. */ -}} + - apiGroups: [""] + resources: + - "secrets" + verbs: ["get", "update", "patch"] + resourceNames: [ {{ include "newrelic-infra-operator.fullname.config" . | quote }} ] + {{/* resourceNames used above do not support "create" verb. */ -}} + - apiGroups: [""] + resources: + - "secrets" + verbs: ["create"] + {{/* "list" and "watch" are required for controller-runtime caching. */ -}} + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterrolebindings"] + verbs: ["list", "watch", "get"] + {{/* Our controller needs permission to add the ServiceAccounts from the user to the -infra-agent CRB. */ -}} + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["clusterrolebindings"] + verbs: ["update"] + resourceNames: [ {{ include "newrelic-infra-operator.fullname.infra-agent" . | quote }} ] + {{- /* Controller must have permissions it will grant to other ServiceAccounts. */ -}} + {{- include "newrelic-infra-operator.infra-agent-monitoring-rules" . | nindent 2 }} +--- +{{/* infra-agent is the ClusterRole to be used by the injected agents to get metrics */}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic-infra-operator.fullname.infra-agent" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + {{- include "newrelic-infra-operator.infra-agent-monitoring-rules" . | nindent 2 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..1f5f8b89b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/clusterrolebinding.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +--- +{{/* infra-agent is the ClusterRoleBinding to be used by the ServiceAccounts of the injected agents */}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic-infra-operator.fullname.infra-agent" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic-infra-operator.fullname.infra-agent" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/configmap.yaml new file mode 100644 index 000000000..fdb4a1e3b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-infra-operator.fullname.config" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + operator.yaml: {{- include "newrelic-infra-operator.configmap.data" . | toYaml | nindent 4 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/deployment.yaml new file mode 100644 index 000000000..40f389887 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/deployment.yaml @@ -0,0 +1,92 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.podAnnotations }} + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ template "newrelic.common.serviceAccount.name" . }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.image.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "newrelic.common.naming.name" . }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 10 }} + {{- end }} + env: + - name: CLUSTER_NAME + value: {{ include "newrelic.common.cluster" . }} + - name: NRIA_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + volumeMounts: + - name: config + mountPath: /etc/newrelic/newrelic-infra-operator/ + - name: tls-key-cert-pair + mountPath: /tmp/k8s-webhook-server/serving-certs/ + readinessProbe: + httpGet: + path: /healthz + port: 9440 + initialDelaySeconds: 1 + periodSeconds: 1 + {{- if .Values.resources }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "newrelic-infra-operator.fullname.config" . }} + - name: tls-key-cert-pair + secret: + secretName: {{ include "newrelic-infra-operator.fullname.admission" . }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 -}} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 -}} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + {{- if include "newrelic.common.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/secret.yaml @@ -0,0 +1,2 @@ +{{- /* Common library will take care of creating the secret or not. */}} +{{- include "newrelic.common.license.secret" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/service.yaml new file mode 100644 index 000000000..04af4d09c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + ports: + - port: 443 + targetPort: 9443 + selector: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/serviceaccount.yaml new file mode 100644 index 000000000..b1e74523e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if include "newrelic.common.serviceAccount.annotations" . }} + annotations: + {{- include "newrelic.common.serviceAccount.annotations" . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/deployment_test.yaml new file mode 100644 index 000000000..a1ffa88d0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/deployment_test.yaml @@ -0,0 +1,32 @@ +suite: test cluster environment variable setup +templates: + - templates/deployment.yaml + - templates/configmap.yaml + - templates/secret.yaml +release: + name: my-release + namespace: my-namespac +tests: + - it: has a linux node selector by default + set: + cluster: my-cluster + licenseKey: use-whatever + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + template: templates/deployment.yaml + - it: has a linux node selector and additional selectors + set: + cluster: my-cluster + licenseKey: use-whatever + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue + template: templates/deployment.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml new file mode 100644 index 000000000..78f1b1f6a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_patch_psp_test.yaml @@ -0,0 +1,23 @@ +suite: test rendering for PSPs +templates: + - templates/admission-webhooks/job-patch/psp.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: If PSPs are enabled PodSecurityPolicy is rendered + set: + cluster: test-cluster + licenseKey: use-whatever + rbac: + pspEnabled: true + asserts: + - hasDocuments: + count: 1 + - it: If PSPs are disabled PodSecurityPolicy isn't rendered + set: + cluster: test-cluster + licenseKey: use-whatever + asserts: + - hasDocuments: + count: 0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml new file mode 100644 index 000000000..c6acda2db --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/job_serviceaccount_test.yaml @@ -0,0 +1,64 @@ +suite: test job' serviceAccount +templates: + - templates/admission-webhooks/job-patch/job-createSecret.yaml + - templates/admission-webhooks/job-patch/job-patchWebhook.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: my-release-newrelic-infra-operator-admission + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + + - it: has a linux node selector by default + set: + cluster: my-cluster + licenseKey: use-whatever + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + + - it: has a linux node selector and additional selectors + set: + cluster: my-cluster + licenseKey: use-whatever + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/rbac_test.yaml new file mode 100644 index 000000000..03473cb39 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/tests/rbac_test.yaml @@ -0,0 +1,41 @@ +suite: test RBAC creation +templates: + - templates/admission-webhooks/job-patch/rolebinding.yaml + - templates/admission-webhooks/job-patch/clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: subjects[0].name + value: my-release-newrelic-infra-operator-admission + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: subjects[0].name + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + licenseKey: use-whatever + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: subjects[0].name + value: default diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/values.yaml new file mode 100644 index 000000000..3dd6fd055 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infra-operator/values.yaml @@ -0,0 +1,222 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Mandatory. Can be configured also with `global.cluster` +cluster: "" +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" +# -- In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` +customSecretName: "" +# -- In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` +customSecretLicenseKey: "" + +# -- Image for the New Relic Infrastructure Operator +# @default -- See `values.yaml` +image: + repository: newrelic/newrelic-infra-operator + tag: "" + pullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + +# -- Image used to create certificates and inject them to the admission webhook +# @default -- See `values.yaml` +admissionWebhooksPatchJob: + image: + registry: # Defaults to registry.k8s.io + repository: ingress-nginx/kube-webhook-certgen + tag: v1.3.0 + pullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + + # -- Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies. + # Enforce a read-only root. + volumeMounts: [] + # - name: tmp + # mountPath: /tmp + # -- Volumes to add to the job container. + volumes: [] + # - name: tmp + # emptyDir: {} + +rbac: + # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects. + pspEnabled: false + +replicas: 1 + +# -- Resources available for this pod +resources: + limits: + memory: 80M + requests: + cpu: 100m + memory: 30M + +# -- Settings controlling ServiceAccount creation +# @default -- See `values.yaml` +serviceAccount: + # serviceAccount.create -- (bool) Specifies whether a ServiceAccount should be created + # @default -- `true` + create: + # If not set and create is true, a name is generated using the fullname template + name: "" + # Specify any annotations to add to the ServiceAccount + annotations: + +# -- Annotations to add to the pod. +podAnnotations: {} + +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- `false` +hostNetwork: +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: + fsGroup: 1001 + runAsUser: 1001 + runAsGroup: 1001 +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +# -- Sets pod/node affinities. Can be configured also with `global.affinity` +affinity: {} +# -- Sets pod's node selector. Can be configured also with `global.nodeSelector` +nodeSelector: {} +# -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` +tolerations: [] + +certManager: + # certManager.enabled -- Use cert manager for webhook certs + enabled: false + +# -- Webhook timeout +# Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts +timeoutSeconds: 10 + +# -- Operator configuration +# @default -- See `values.yaml` +config: + # -- IgnoreMutationErrors instruments the operator to ignore injection error instead of failing. + # If set to false errors of the injection could block the creation of pods. + ignoreMutationErrors: true + + # -- configuration of the sidecar injection webhook + # @default -- See `values.yaml` + infraAgentInjection: +# policies: +# - podSelector: +# matchExpressions: +# - key: app +# operator: In +# values: [ "nginx-sidecar" ] +# + # All policies are ORed, if one policy matches the sidecar is injected. + # Within a policy PodSelectors, NamespaceSelector and NamespaceName are ANDed, any of these, if not specified, is ignored. + # The following policy is injected if global.fargate=true and matches all pods belonging to any fargate profile. + # policies: + # - podSelector: + # matchExpressions: + # - key: "eks.amazonaws.com/fargate-profile" + # operator: Exists + # Also NamespaceName and NamespaceSelector can be leveraged. + # namespaceName: "my-namespace" + # namespaceSelector: {} + + # -- agentConfig contains the configuration for the container agent injected + # @default -- See `values.yaml` + agentConfig: + # Custom Attributes allows to pass any custom attribute to the injected infra agents. + # The value is computed either from the defaultValue or taken at injected time from Label specified in "fromLabel". + # Either the label should exist or the default should be specified in order to have the injection working. + # customAttributes: + # - name: computeType + # defaultValue: serverless + # - name: fargateProfile + # fromLabel: eks.amazonaws.com/fargate-profile + + # -- Image of the infrastructure agent to be injected. + # @default -- See `values.yaml` + image: + repository: newrelic/infrastructure-k8s + tag: 2.13.15-unprivileged + pullPolicy: IfNotPresent + + # -- configSelectors is the way to configure resource requirements and extra envVars of the injected sidecar container. + # When mutating it will be applied the first configuration having the labelSelector matching with the mutating pod. + # @default -- See `values.yaml` + configSelectors: + - resourceRequirements: # resourceRequirements to apply to the injected sidecar. + limits: + memory: 100M + cpu: 200m + requests: + memory: 50M + cpu: 100m + extraEnvVars: # extraEnvVars to pass to the injected sidecar. + DISABLE_KUBE_STATE_METRICS: "true" + # NRIA_VERBOSE: "1" + labelSelector: + matchExpressions: + - key: "app.kubernetes.io/name" + operator: NotIn + values: ["kube-state-metrics"] + - key: "app" + operator: NotIn + values: ["kube-state-metrics"] + - key: "k8s-app" + operator: NotIn + values: ["kube-state-metrics"] + + - resourceRequirements: + limits: + memory: 300M + cpu: 300m + requests: + memory: 150M + cpu: 150m + labelSelector: + matchLabels: + k8s-app: kube-state-metrics + # extraEnvVars: + # NRIA_VERBOSE: "1" + + - resourceRequirements: + limits: + memory: 300M + cpu: 300m + requests: + memory: 150M + cpu: 150m + labelSelector: + matchLabels: + app: kube-state-metrics + # extraEnvVars: + # NRIA_VERBOSE: "1" + + - resourceRequirements: + limits: + memory: 300M + cpu: 300m + requests: + memory: 150M + cpu: 150m + labelSelector: + matchLabels: + app.kubernetes.io/name: kube-state-metrics + # extraEnvVars: + # NRIA_VERBOSE: "1" + + # pod Security Context of the sidecar injected. + # Notice that ReadOnlyRootFilesystem and AllowPrivilegeEscalation enforced respectively to true and to false. + # podSecurityContext: + # RunAsUser: + # RunAsGroup: diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/.helmignore new file mode 100644 index 000000000..2bfa6a4d9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/.helmignore @@ -0,0 +1 @@ +tests/ diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.lock new file mode 100644 index 000000000..51857821f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T23:46:25.952459233Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.yaml new file mode 100644 index 000000000..7555275e6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +appVersion: 3.30.0 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic Kubernetes monitoring solution +home: https://docs.newrelic.com/docs/kubernetes-pixie/kubernetes-integration/get-started/introduction-kubernetes-integration/ +icon: https://newrelic.com/themes/custom/curio/assets/mediakit/NR_logo_Horizontal.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: newrelic-infrastructure +sources: +- https://github.com/newrelic/nri-kubernetes/ +- https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure +- https://github.com/newrelic/infrastructure-agent/ +version: 3.35.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md new file mode 100644 index 000000000..247f62e63 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md @@ -0,0 +1,220 @@ +# newrelic-infrastructure + +A Helm chart to deploy the New Relic Kubernetes monitoring solution + +**Homepage:** + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-kubernetes https://newrelic.github.io/nri-kubernetes +helm upgrade --install newrelic-infrastructure nri-kubernetes/newrelic-infrastructure -f your-custom-values.yaml +``` + +## Source Code + +* +* +* + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Low data mode +There are two mechanisms to reduce the amount of data that this integration sends to New Relic. See this snippet from the `values.yaml` file: +```yaml +common: + config: + interval: 15s + +lowDataMode: false +``` + +The `lowDataMode` toggle is the simplest way to reduce data send to Newrelic. Setting it to `true` changes the default scrape interval from 15 seconds +(the default) to 30 seconds. + +If you need for some reason to fine-tune the number of seconds you can use `common.config.interval` directly. If you take a look at the `values.yaml` +file, the value there is `nil`. If any value is set there, the `lowDataMode` toggle is ignored as this value takes precedence. + +Setting this interval above 40 seconds can make you experience issues with the Kubernetes Cluster Explorer so this chart limits setting the interval +inside the range of 10 to 40 seconds. + +### Affinities and tolerations + +The New Relic common library allows to set affinities, tolerations, and node selectors globally using e.g. `.global.affinity` to ease the configuration +when you use this chart using `nri-bundle`. This chart has an extra level of granularity to the components that it deploys: +control plane, ksm, and kubelet. + +Take this snippet as an example: +```yaml +global: + affinity: {} +affinity: {} + +kubelet: + affinity: {} +ksm: + affinity: {} +controlPlane: + affinity: {} +``` + +The order to set an affinity is to set first any `kubelet.affinity`, `ksm.affinity`, or `controlPlane.affinity`. If these values are empty the chart +fallbacks to `affinity` (at root level), and if that value is empty, the chart fallbacks to `global.affinity`. + +The same procedure applies to `nodeSelector` and `tolerations`. + +On the other hand, some components have affinities and tolerations predefined e.g. to be able to run kubelet pods on nodes that are tainted as control plane +nodes or to schedule the KSM scraper on the same node of KSM to reduce the inter-node traffic. + +If you are having problems assigning pods to nodes it may be because of this. Take a look at the [`values.yaml`](values.yaml) to see if the pod that is +not having your expected behavior has any predefined value. + +### `hostNetwork` toggle + +In versions below v3, changing the `privileged` mode affected the `hostNetwork`. We changed this behavior and now you can set pods to use `hostNetwork` +using the corresponding [flags from the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) +(`.global.hostNetwork` and `.hostNetwork`) but the component that scrapes data from the control plane has always set `hostNetwork` enabled by default +(Look in the [`values.yaml`](values.yaml) for `controlPlane.hostNetwork: true`) + +This is because the most common configuration of the control plane components is to be configured to listen only to `localhost`. + +If your cluster security policy does not allow to use `hostNetwork`, you can disable it control plane monitoring by setting `controlPlane.enabled` to +`false.` + +### `privileged` toggle + +The default value for `privileged` [from the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) is +`false` but in this particular this chart it is set to `true` (Look in the [`values.yaml`](values.yaml) for `privileged: true`) + +This is because when `kubelet` pods need to run in privileged mode to fetch cpu, memory, process, and network metrics of your nodes. + +If your cluster security policy does not allow to have `privileged` in your pod' security context, you can disable it by setting `privileged` to +`false` taking into account that you will lose all the metrics from the host and some metadata from the host that are added to the metrics of the +integrations that you have configured. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets pod/node affinities set almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` | +| common | object | See `values.yaml` | Config that applies to all instances of the solution: kubelet, ksm, control plane and sidecars. | +| common.agentConfig | object | `{}` | Config for the Infrastructure agent. Will be used by the forwarder sidecars and the agent running integrations. See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ | +| common.config.interval | duration | `15s` (See [Low data mode](README.md#low-data-mode)) | Intervals larger than 40s are not supported and will cause the NR UI to not behave properly. Any non-nil value will override the `lowDataMode` default. | +| common.config.namespaceSelector | object | `{}` | Config for filtering ksm and kubelet metrics by namespace. | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| controlPlane | object | See `values.yaml` | Configuration for the control plane scraper. | +| controlPlane.affinity | object | Deployed only in control plane nodes. | Affinity for the control plane DaemonSet. | +| controlPlane.agentConfig | object | `{}` | Config for the Infrastructure agent that will forward the metrics to the backend. It will be merged with the configuration in `.common.agentConfig` See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ | +| controlPlane.config.apiServer | object | Common settings for most K8s distributions. | API Server monitoring configuration | +| controlPlane.config.apiServer.enabled | bool | `true` | Enable API Server monitoring | +| controlPlane.config.controllerManager | object | Common settings for most K8s distributions. | Controller manager monitoring configuration | +| controlPlane.config.controllerManager.enabled | bool | `true` | Enable controller manager monitoring. | +| controlPlane.config.etcd | object | Common settings for most K8s distributions. | etcd monitoring configuration | +| controlPlane.config.etcd.enabled | bool | `true` | Enable etcd monitoring. Might require manual configuration in some environments. | +| controlPlane.config.retries | int | `3` | Number of retries after timeout expired | +| controlPlane.config.scheduler | object | Common settings for most K8s distributions. | Scheduler monitoring configuration | +| controlPlane.config.scheduler.enabled | bool | `true` | Enable scheduler monitoring. | +| controlPlane.config.timeout | string | `"10s"` | Timeout for the Kubernetes APIs contacted by the integration | +| controlPlane.enabled | bool | `true` | Deploy control plane monitoring component. | +| controlPlane.hostNetwork | bool | `true` | Run Control Plane scraper with `hostNetwork`. `hostNetwork` is required for most control plane configurations, as they only accept connections from localhost. | +| controlPlane.kind | string | `"DaemonSet"` | How to deploy the control plane scraper. If autodiscovery is in use, it should be `DaemonSet`. Advanced users using static endpoints set this to `Deployment` to avoid reporting metrics twice. | +| controlPlane.tolerations | list | Schedules in all tainted nodes | Tolerations for the control plane DaemonSet. | +| customAttributes | object | `{}` | Adds extra attributes to the cluster and all the metrics emitted to the backend. Can be configured also with `global.customAttributes` | +| customSecretLicenseKey | string | `""` | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` | +| customSecretName | string | `""` | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| enableProcessMetrics | bool | `false` | Collect detailed metrics from processes running in the host. This defaults to true for accounts created before July 20, 2020. ref: https://docs.newrelic.com/docs/release-notes/infrastructure-release-notes/infrastructure-agent-release-notes/new-relic-infrastructure-agent-1120 | +| fedramp.enabled | bool | `false` | Enables FedRAMP. Can be configured also with `global.fedramp.enabled` | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | `false` | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| images | object | See `values.yaml` | Images used by the chart for the integration and agents. | +| images.agent | object | See `values.yaml` | Image for the New Relic Infrastructure Agent plus integrations. | +| images.forwarder | object | See `values.yaml` | Image for the New Relic Infrastructure Agent sidecar. | +| images.integration | object | See `values.yaml` | Image for the New Relic Kubernetes integration. | +| images.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| integrations | object | `{}` | Config files for other New Relic integrations that should run in this cluster. | +| ksm | object | See `values.yaml` | Configuration for the Deployment that collects state metrics from KSM (kube-state-metrics). | +| ksm.affinity | object | Deployed in the same node as KSM | Affinity for the KSM Deployment. | +| ksm.agentConfig | object | `{}` | Config for the Infrastructure agent that will forward the metrics to the backend. It will be merged with the configuration in `.common.agentConfig` See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ | +| ksm.config.retries | int | `3` | Number of retries after timeout expired | +| ksm.config.scheme | string | `"http"` | Scheme to use to connect to kube-state-metrics. Supported values are `http` and `https`. | +| ksm.config.selector | string | `"app.kubernetes.io/name=kube-state-metrics"` | Label selector that will be used to automatically discover an instance of kube-state-metrics running in the cluster. | +| ksm.config.timeout | string | `"10s"` | Timeout for the ksm API contacted by the integration | +| ksm.enabled | bool | `true` | Enable cluster state monitoring. Advanced users only. Setting this to `false` is not supported and will break the New Relic experience. | +| ksm.hostNetwork | bool | Not set | Sets pod's hostNetwork. When set bypasses global/common variable | +| ksm.resources | object | 100m/150M -/850M | Resources for the KSM scraper pod. Keep in mind that sharding is not supported at the moment, so memory usage for this component ramps up quickly on large clusters. | +| ksm.tolerations | list | Schedules in all tainted nodes | Tolerations for the KSM Deployment. | +| kubelet | object | See `values.yaml` | Configuration for the DaemonSet that collects metrics from the Kubelet. | +| kubelet.agentConfig | object | `{}` | Config for the Infrastructure agent that will forward the metrics to the backend and will run the integrations in this cluster. It will be merged with the configuration in `.common.agentConfig`. You can see all the agent configurations in [New Relic docs](https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/) e.g. you can set `passthrough_environment` int the [config file](https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/configure-infrastructure-agent/#config-file) so the agent let use that environment variables to the integrations. | +| kubelet.config.retries | int | `3` | Number of retries after timeout expired | +| kubelet.config.scraperMaxReruns | int | `4` | Max number of scraper rerun when scraper runtime error happens | +| kubelet.config.timeout | string | `"10s"` | Timeout for the kubelet APIs contacted by the integration | +| kubelet.enabled | bool | `true` | Enable kubelet monitoring. Advanced users only. Setting this to `false` is not supported and will break the New Relic experience. | +| kubelet.extraEnv | list | `[]` | Add user environment variables to the agent | +| kubelet.extraEnvFrom | list | `[]` | Add user environment from configMaps or secrets as variables to the agent | +| kubelet.extraVolumeMounts | list | `[]` | Defines where to mount volumes specified with `extraVolumes` | +| kubelet.extraVolumes | list | `[]` | Volumes to mount in the containers | +| kubelet.hostNetwork | bool | Not set | Sets pod's hostNetwork. When set bypasses global/common variable | +| kubelet.tolerations | list | Schedules in all tainted nodes | Tolerations for the control plane DaemonSet. | +| labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| lowDataMode | bool | `false` (See [Low data mode](README.md#low-data-mode)) | Send less data by incrementing the interval from `15s` (the default when `lowDataMode` is `false` or `nil`) to `30s`. Non-nil values of `common.config.interval` will override this value. | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| nrStaging | bool | `false` | Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` | +| podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. | +| podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` | +| podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| privileged | bool | `true` | Run the integration with full access to the host filesystem and network. Running in this mode allows reporting fine-grained cpu, memory, process and network metrics for your nodes. | +| proxy | string | `""` | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` | +| rbac.create | bool | `true` | Whether the chart should automatically create the RBAC objects required to run. | +| rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | +| selfMonitoring.pixie.enabled | bool | `false` | Enables the Pixie Health Check nri-flex config. This Flex config performs periodic checks of the Pixie /healthz and /statusz endpoints exposed by the Pixie Cloud Connector. A status for each endpoint is sent to New Relic in a pixieHealthCheck event. | +| serviceAccount | object | See `values.yaml` | Settings controlling ServiceAccount creation. | +| serviceAccount.create | bool | `true` | Whether the chart should automatically create the ServiceAccount objects required to run. | +| sink.http.probeBackoff | string | `"5s"` | The amount of time the scraper container to backoff when it fails to probe infra agent sidecar. | +| sink.http.probeTimeout | string | `"90s"` | The amount of time the scraper container to probe infra agent sidecar container before giving up and restarting during pod starts. | +| strategy | object | `type: Recreate` | Update strategy for the deployed Deployments. | +| tolerations | list | `[]` | Sets pod's tolerations to node taints almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| updateStrategy | object | See `values.yaml` | Update strategy for the deployed DaemonSets. | +| verboseLog | bool | `false` | Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` | + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) + +## Past Contributors + +Previous iterations of this chart started as a community project in the [stable Helm chart repository](github.com/helm/charts/). New Relic is very thankful for all the 15+ community members that contributed and helped maintain the chart there over the years: + +* coreypobrien +* sstarcher +* jmccarty3 +* slayerjain +* ryanhope2 +* rk295 +* michaelajr +* isindir +* idirouhab +* ismferd +* enver +* diclophis +* jeffdesc +* costimuraru +* verwilst +* ezelenka diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md.gotmpl new file mode 100644 index 000000000..32fac5c23 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/README.md.gotmpl @@ -0,0 +1,137 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-kubernetes https://newrelic.github.io/nri-kubernetes +helm upgrade --install newrelic-infrastructure nri-kubernetes/newrelic-infrastructure -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Low data mode +There are two mechanisms to reduce the amount of data that this integration sends to New Relic. See this snippet from the `values.yaml` file: +```yaml +common: + config: + interval: 15s + +lowDataMode: false +``` + +The `lowDataMode` toggle is the simplest way to reduce data send to Newrelic. Setting it to `true` changes the default scrape interval from 15 seconds +(the default) to 30 seconds. + +If you need for some reason to fine-tune the number of seconds you can use `common.config.interval` directly. If you take a look at the `values.yaml` +file, the value there is `nil`. If any value is set there, the `lowDataMode` toggle is ignored as this value takes precedence. + +Setting this interval above 40 seconds can make you experience issues with the Kubernetes Cluster Explorer so this chart limits setting the interval +inside the range of 10 to 40 seconds. + +### Affinities and tolerations + +The New Relic common library allows to set affinities, tolerations, and node selectors globally using e.g. `.global.affinity` to ease the configuration +when you use this chart using `nri-bundle`. This chart has an extra level of granularity to the components that it deploys: +control plane, ksm, and kubelet. + +Take this snippet as an example: +```yaml +global: + affinity: {} +affinity: {} + +kubelet: + affinity: {} +ksm: + affinity: {} +controlPlane: + affinity: {} +``` + +The order to set an affinity is to set first any `kubelet.affinity`, `ksm.affinity`, or `controlPlane.affinity`. If these values are empty the chart +fallbacks to `affinity` (at root level), and if that value is empty, the chart fallbacks to `global.affinity`. + +The same procedure applies to `nodeSelector` and `tolerations`. + +On the other hand, some components have affinities and tolerations predefined e.g. to be able to run kubelet pods on nodes that are tainted as control plane +nodes or to schedule the KSM scraper on the same node of KSM to reduce the inter-node traffic. + +If you are having problems assigning pods to nodes it may be because of this. Take a look at the [`values.yaml`](values.yaml) to see if the pod that is +not having your expected behavior has any predefined value. + +### `hostNetwork` toggle + +In versions below v3, changing the `privileged` mode affected the `hostNetwork`. We changed this behavior and now you can set pods to use `hostNetwork` +using the corresponding [flags from the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) +(`.global.hostNetwork` and `.hostNetwork`) but the component that scrapes data from the control plane has always set `hostNetwork` enabled by default +(Look in the [`values.yaml`](values.yaml) for `controlPlane.hostNetwork: true`) + +This is because the most common configuration of the control plane components is to be configured to listen only to `localhost`. + +If your cluster security policy does not allow to use `hostNetwork`, you can disable it control plane monitoring by setting `controlPlane.enabled` to +`false.` + +### `privileged` toggle + +The default value for `privileged` [from the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) is +`false` but in this particular this chart it is set to `true` (Look in the [`values.yaml`](values.yaml) for `privileged: true`) + +This is because when `kubelet` pods need to run in privileged mode to fetch cpu, memory, process, and network metrics of your nodes. + +If your cluster security policy does not allow to have `privileged` in your pod' security context, you can disable it by setting `privileged` to +`false` taking into account that you will lose all the metrics from the host and some metadata from the host that are added to the metrics of the +integrations that you have configured. + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + +## Past Contributors + +Previous iterations of this chart started as a community project in the [stable Helm chart repository](github.com/helm/charts/). New Relic is very thankful for all the 15+ community members that contributed and helped maintain the chart there over the years: + +* coreypobrien +* sstarcher +* jmccarty3 +* slayerjain +* ryanhope2 +* rk295 +* michaelajr +* isindir +* idirouhab +* ismferd +* enver +* diclophis +* jeffdesc +* costimuraru +* verwilst +* ezelenka diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml new file mode 100644 index 000000000..1e2c36d21 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-cplane-kind-deployment-values.yaml @@ -0,0 +1,135 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +common: + agentConfig: + # We set it in order for the kubelet to not crash when posting tho the agent. Since the License_Key is + # not valid, the Identity Api doesn't return an AgentID and the server from the Agent takes to long to respond + is_forward_only: true + config: + sink: + http: + timeout: 180s + +customAttributes: + new: relic + loren: ipsum + +# Disable KSM scraper as it is not enabled when testing this chart individually. +ksm: + enabled: false + +# K8s DaemonSets update strategy. +updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + +enableProcessMetrics: "false" +serviceAccount: + create: true + +podAnnotations: + annotation1: "annotation" +podLabels: + label1: "label" + +securityContext: + runAsUser: 1000 + runAsGroup: 2000 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + +privileged: true + +rbac: + create: true + pspEnabled: false + +prefixDisplayNameWithCluster: false +useNodeNameAsDisplayName: true +integrations_config: [] + +kubelet: + enabled: true + annotations: {} + tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + extraEnv: + - name: ENV_VAR1 + value: "var1" + - name: ENV_VAR2 + value: "var2" + resources: + limits: + memory: 400M + requests: + cpu: 100m + memory: 180M + config: + scheme: "http" + +controlPlane: + kind: Deployment + enabled: true + config: + etcd: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=etcd" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + - url: http://localhost:2381 + scheduler: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-scheduler" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + controllerManager: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-controller-manager" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + mtls: + secretName: secret-name + secretNamespace: default + apiServer: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-apiserver" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + mtls: + secretName: secret-name4 + - url: http://localhost:8080 + +images: + integration: + tag: test + repository: e2e/nri-kubernetes diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-values.yaml new file mode 100644 index 000000000..125a49607 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/ci/test-values.yaml @@ -0,0 +1,134 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +common: + agentConfig: + # We set it in order for the kubelet to not crash when posting tho the agent. Since the License_Key is + # not valid, the Identity Api doesn't return an AgentID and the server from the Agent takes to long to respond + is_forward_only: true + config: + sink: + http: + timeout: 180s + +customAttributes: + new: relic + loren: ipsum + +# Disable KSM scraper as it is not enabled when testing this chart individually. +ksm: + enabled: false + +# K8s DaemonSets update strategy. +updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + +enableProcessMetrics: "false" +serviceAccount: + create: true + +podAnnotations: + annotation1: "annotation" +podLabels: + label1: "label" + +securityContext: + runAsUser: 1000 + runAsGroup: 2000 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + +privileged: true + +rbac: + create: true + pspEnabled: false + +prefixDisplayNameWithCluster: false +useNodeNameAsDisplayName: true +integrations_config: [] + +kubelet: + enabled: true + annotations: {} + tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + extraEnv: + - name: ENV_VAR1 + value: "var1" + - name: ENV_VAR2 + value: "var2" + resources: + limits: + memory: 400M + requests: + cpu: 100m + memory: 180M + config: + scheme: "http" + +controlPlane: + enabled: true + config: + etcd: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=etcd" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + - url: http://localhost:2381 + scheduler: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-scheduler" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + controllerManager: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-controller-manager" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + mtls: + secretName: secret-name + secretNamespace: default + apiServer: + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-apiserver" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + mtls: + secretName: secret-name4 + - url: http://localhost:8080 + +images: + integration: + tag: test + repository: e2e/nri-kubernetes diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/NOTES.txt new file mode 100644 index 000000000..16cc6ea13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/NOTES.txt @@ -0,0 +1,131 @@ +{{- if not .Values.forceUnsupportedInterval }} +{{- $max := 40 }} +{{- $min := 10 }} +{{- if not (.Values.common.config.interval | hasSuffix "s") }} +{{ fail (printf "Interval must be between %ds and %ds" $min $max ) }} +{{- end }} +{{- if gt ( .Values.common.config.interval | trimSuffix "s" | int64 ) $max }} +{{ fail (printf "Intervals larger than %ds are not supported" $max) }} +{{- end }} +{{- if lt ( .Values.common.config.interval | trimSuffix "s" | int64 ) $min }} +{{ fail (printf "Intervals smaller than %ds are not supported" $min) }} +{{- end }} +{{- end }} + +{{- if or (not .Values.ksm.enabled) (not .Values.kubelet.enabled) }} +Warning: +======== + +You have specified ksm or kubelet integration components as not enabled. +Those components are needed to have the full experience on NROne kubernetes explorer. +{{- end }} + +{{- if and .Values.controlPlane.enabled (not (include "nriKubernetes.controlPlane.hostNetwork" .)) }} +Warning: +======== + +Most Control Plane components listen in the loopback address only, which is not reachable without `hostNetwork: true`. +Control plane autodiscovery might not work as expected. +You can enable hostNetwork for all pods by setting `global.hotNetwork`, `hostNetwork` or only for the control +plane pods by setting `controlPlane.hostNetwork: true`. Alternatively, you can disable control plane monitoring altogether with +`controlPlane.enabled: false`. +{{- end }} + +{{- if and (include "newrelic.fargate" .) .Values.kubelet.affinity }} +Warning: +======== + +You have specified both an EKS Fargate environment (global.fargate) and custom +nodeAffinity rules, so we couldn't automatically exclude the kubelet daemonSet from +Fargate nodes. In order for the integration to work, you MUST manually exclude +the daemonSet from Fargate nodes. + +Please make sure your `values.yaml' contains a .kubelet.affinity.nodeAffinity that achieve the same effect as: + +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: eks.amazonaws.com/compute-type + operator: NotIn + values: + - fargate +{{- end }} + +{{- if and .Values.nodeAffinity .Values.controlPlane.enabled }} +WARNING: `nodeAffinity` is deprecated +===================================== + +We have applied the old `nodeAffinity` to KSM and Kubelet components, but *NOT* to the control plane component as it +might conflict with the default nodeSelector. +This shimming will be removed in the future, please convert your `nodeAffinity` item into: +`ksm.affinity.nodeAffinity`, `controlPlane.affinity.nodeAffinity`, and `kubelet.affinity.nodeAffinity`. +{{- end }} + +{{- if and .Values.integrations_config }} +WARNING: `integrations_config` is deprecated +============================================ + +We have automatically translated `integrations_config` to the new format, but this shimming will be removed in the +future. Please migrate your configs to the new format in the `integrations` key. +{{- end }} + +{{- if or .Values.kubeStateMetricsScheme .Values.kubeStateMetricsPort .Values.kubeStateMetricsUrl .Values.kubeStateMetricsPodLabel .Values.kubeStateMetricsNamespace }} +WARNING: `kubeStateMetrics*` are deprecated +=========================================== + +We have automatically translated your `kubeStateMetrics*` values to the new format, but this shimming will be removed in +the future. Please migrate your configs to the new format in the `ksm.config` key. +{{- end }} + +{{- if .Values.runAsUser }} +WARNING: `runAsUser` is deprecated +================================== + +We have automatically translated your `runAsUser` setting to the new format, but this shimming will be removed in the +future. Please migrate your configs to the new format in the `securityContext` key. +{{- end }} + +{{- if .Values.config }} +WARNING: `config` is deprecated +=============================== + +We have automatically translated your `config` setting to the new format, but this shimming will be removed in the +future. Please migrate your agent config to the new format in the `common.agentConfig` key. +{{- end }} + + +{{ $errors:= "" }} + +{{- if .Values.logFile }} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.logFile" . ) }} +{{- end }} + +{{- if .Values.resources }} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.resources" . ) }} +{{- end }} + +{{- if .Values.image }} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.image" . ) }} +{{- end }} + +{{- if .Values.enableWindows }} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.windows" . ) }} +{{- end }} + +{{- if ( or .Values.controllerManagerEndpointUrl .Values.schedulerEndpointUrl .Values.etcdEndpointUrl .Values.apiServerEndpointUrl )}} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.apiURL" . ) }} +{{- end }} + +{{- if ( or .Values.etcdTlsSecretName .Values.etcdTlsSecretNamespace )}} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.etcdSecrets" . ) }} +{{- end }} + +{{- if .Values.apiServerSecurePort }} +{{ $errors = printf "%s\n\n%s" $errors (include "newrelic.compatibility.message.apiServerSecurePort" . ) }} +{{- end }} + +{{- if $errors | trim}} +{{- fail (printf "\n\n%s\n%s" (include "newrelic.compatibility.message.common" . ) $errors ) }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers.tpl new file mode 100644 index 000000000..033ef0bfc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers.tpl @@ -0,0 +1,118 @@ +{{/* +Create a default fully qualified app name. + +This is a copy and paste from the common-library's name helper because the overriding system was broken. +As we have to change the logic to use "nrk8s" instead of `.Chart.Name` we need to maintain here a version +of the fullname helper + +By default the full name will be "" just in if it has "nrk8s" included in that, if not +it will be concatenated like "-nrk8s". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "nriKubernetes.naming.fullname" -}} +{{- $name := .Values.nameOverride | default "nrk8s" -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end -}} + + + +{{- /* Naming helpers*/ -}} +{{- define "nriKubernetes.naming.secrets" }} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "secrets") -}} +{{- end -}} + + + +{{- /* Return a YAML with the mode to be added to the labels */ -}} +{{- define "nriKubernetes._mode" -}} +{{- if include "newrelic.common.privileged" . -}} + mode: privileged +{{- else -}} + mode: unprivileged +{{- end -}} +{{- end -}} + + + +{{/* +Add `mode` label to the labels that come from the common library for all the objects +*/}} +{{- define "nriKubernetes.labels" -}} +{{- $labels := include "newrelic.common.labels" . | fromYaml -}} +{{- $mode := fromYaml ( include "nriKubernetes._mode" . ) -}} + +{{- mustMergeOverwrite $labels $mode | toYaml -}} +{{- end -}} + + + +{{/* +Add `mode` label to the labels that come from the common library for podLabels +*/}} +{{- define "nriKubernetes.labels.podLabels" -}} +{{- $labels := include "newrelic.common.labels.podLabels" . | fromYaml -}} +{{- $mode := fromYaml ( include "nriKubernetes._mode" . ) -}} + +{{- mustMergeOverwrite $labels $mode | toYaml -}} +{{- end -}} + + + +{{/* +Returns fargate +*/}} +{{- define "newrelic.fargate" -}} +{{- if .Values.fargate -}} + {{- .Values.fargate -}} +{{- else if .Values.global -}} + {{- if .Values.global.fargate -}} + {{- .Values.global.fargate -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- define "newrelic.integrationConfigDefaults" -}} +{{- if include "newrelic.common.lowDataMode" . -}} +interval: 30s +{{- else -}} +interval: 15s +{{- end -}} +{{- end -}} + + + +{{- /* These are the defaults that are used for all the containers in this chart (except the kubelet's agent */ -}} +{{- define "nriKubernetes.securityContext.containerDefaults" -}} +runAsUser: 1000 +runAsGroup: 2000 +allowPrivilegeEscalation: false +readOnlyRootFilesystem: true +{{- end -}} + + + +{{- /* Allow to change pod defaults dynamically based if we are running in privileged mode or not */ -}} +{{- define "nriKubernetes.securityContext.container" -}} +{{- $defaults := fromYaml ( include "nriKubernetes.securityContext.containerDefaults" . ) -}} +{{- $compatibilityLayer := include "newrelic.compatibility.securityContext" . | fromYaml -}} +{{- $commonLibrary := include "newrelic.common.securityContext.container" . | fromYaml -}} + +{{- $finalSecurityContext := dict -}} +{{- if $commonLibrary -}} + {{- $finalSecurityContext = mustMergeOverwrite $commonLibrary $compatibilityLayer -}} +{{- else -}} + {{- $finalSecurityContext = mustMergeOverwrite $defaults $compatibilityLayer -}} +{{- end -}} + +{{- toYaml $finalSecurityContext -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl new file mode 100644 index 000000000..07365e5a1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/_helpers_compatibility.tpl @@ -0,0 +1,202 @@ +{{/* +Returns true if .Values.ksm.enabled is true and the legacy disableKubeStateMetrics is not set +*/}} +{{- define "newrelic.compatibility.ksm.enabled" -}} +{{- if and .Values.ksm.enabled (not .Values.disableKubeStateMetrics) -}} +true +{{- end -}} +{{- end -}} + +{{/* +Returns legacy ksm values +*/}} +{{- define "newrelic.compatibility.ksm.legacyData" -}} +enabled: true +{{- if .Values.kubeStateMetricsScheme }} +scheme: {{ .Values.kubeStateMetricsScheme }} +{{- end -}} +{{- if .Values.kubeStateMetricsPort }} +port: {{ .Values.kubeStateMetricsPort }} +{{- end -}} +{{- if .Values.kubeStateMetricsUrl }} +staticURL: {{ .Values.kubeStateMetricsUrl }} +{{- end -}} +{{- if .Values.kubeStateMetricsPodLabel }} +selector: {{ printf "%s=kube-state-metrics" .Values.kubeStateMetricsPodLabel }} +{{- end -}} +{{- if .Values.kubeStateMetricsNamespace }} +namespace: {{ .Values.kubeStateMetricsNamespace}} +{{- end -}} +{{- end -}} + +{{/* +Returns the new value if available, otherwise falling back on the legacy one +*/}} +{{- define "newrelic.compatibility.valueWithFallback" -}} +{{- if .supported }} +{{- toYaml .supported}} +{{- else if .legacy -}} +{{- toYaml .legacy}} +{{- end }} +{{- end -}} + +{{/* +Returns a dictionary with legacy runAsUser config +*/}} +{{- define "newrelic.compatibility.securityContext" -}} +{{- if .Values.runAsUser -}} +{{ dict "runAsUser" .Values.runAsUser | toYaml }} +{{- end -}} +{{- end -}} + +{{/* +Returns legacy annotations if available +*/}} +{{- define "newrelic.compatibility.annotations" -}} +{{- with .Values.daemonSet -}} +{{- with .annotations -}} +{{- toYaml . }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Returns agent configmap merged with legacy config and legacy eventQueueDepth config +*/}} +{{- define "newrelic.compatibility.agentConfig" -}} +{{- $oldConfig := deepCopy (.Values.config | default dict) -}} +{{- $newConfig := deepCopy .Values.common.agentConfig -}} +{{- $eventQueueDepth := dict -}} + +{{- if .Values.eventQueueDepth -}} +{{- $eventQueueDepth = dict "event_queue_depth" .Values.eventQueueDepth -}} +{{- end -}} + +{{- mustMergeOverwrite $oldConfig $newConfig $eventQueueDepth | toYaml -}} +{{- end -}} + +{{- /* +Return a valid podSpec.affinity object from the old `.Values.nodeAffinity`. +*/ -}} +{{- define "newrelic.compatibility.nodeAffinity" -}} +{{- if .Values.nodeAffinity -}} +nodeAffinity: + {{- toYaml .Values.nodeAffinity | nindent 2 }} +{{- end -}} +{{- end -}} + +{{/* +Returns legacy integrations_config configmap data +*/}} +{{- define "newrelic.compatibility.integrations" -}} +{{- if .Values.integrations_config -}} +{{- range .Values.integrations_config }} +{{ .name -}}: |- + {{- toYaml .data | nindent 2 }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic.compatibility.message.logFile" -}} +The 'logFile' option is no longer supported and has been replaced by: + - common.agentConfig.log_file. + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.resources" -}} +You have specified the legacy 'resources' option in your values, which is not fully compatible with the v3 version. +This version deploys three different components and therefore you'll need to specify resources for each of them. +Please use + - ksm.resources, + - controlPlane.resources, + - kubelet.resources. + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.apiServerSecurePort" -}} +You have specified the legacy 'apiServerSecurePort' option in your values, which is not fully compatible with the v3 +version. +Please configure the API Server port as a part of 'apiServer.autodiscover[].endpoints' + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.windows" -}} +nri-kubernetes v3 does not support deploying into windows Nodes. +Please use the latest 2.x version of the chart. + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.etcdSecrets" -}} +Values "etcdTlsSecretName" and "etcdTlsSecretNamespace" are no longer supported, please specify them as a part of the +'etcd' config in the values, for example: + - endpoints: + - url: https://localhost:9979 + insecureSkipVerify: true + auth: + type: mTLS + mtls: + secretName: {{ .Values.etcdTlsSecretName | default "etcdTlsSecretName"}} + secretNamespace: {{ .Values.etcdTlsSecretNamespace | default "etcdTlsSecretNamespace"}} + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.apiURL" -}} +Values "controllerManagerEndpointUrl", "etcdEndpointUrl", "apiServerEndpointUrl", "schedulerEndpointUrl" are no longer +supported, please specify them as a part of the 'controlplane' config in the values, for example + autodiscover: + - selector: "tier=control-plane,component=etcd" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.image" -}} +Configuring image repository an tag under 'image' is no longer supported. +The following values are no longer supported and are currently ignored: + - image.repository + - image.tag + - image.pullPolicy + - image.pullSecrets + +Notice that the 3.x version of the integration uses 3 different images. +Please set: + - images.forwarder.* to configure the infrastructure-agent forwarder. + - images.agent.* to configure the image bundling the infrastructure-agent and on-host integrations. + - images.integration.* to configure the image in charge of scraping k8s data. + +------ +{{- end -}} + +{{- define "newrelic.compatibility.message.customAttributes" -}} +We still support using custom attributes but we support it as a map and dropped it as a string. +customAttributes: {{ .Values.customAttributes | quote }} + +You should change your values to something like this: + +customAttributes: +{{- range $k, $v := fromJson .Values.customAttributes -}} + {{- $k | nindent 2 }}: {{ $v | quote }} +{{- end }} + +**NOTE**: If you read above errors like "invalid character ':' after top-level value" or "json: cannot unmarshal string into Go value of type map[string]interface {}" means that the string you have in your values is not a valid JSON, Helm is not able to parse it and we could not show you how you should change it. Sorry. +{{- end -}} + +{{- define "newrelic.compatibility.message.common" -}} +###### +The chart cannot be rendered since the values listed below are not supported. Please replace those with the new ones compatible with newrelic-infrastructure V3. + +Keep in mind that the flag "--reuse-values" is not supported when migrating from V2 to V3. +Further information can be found in the official docs https://docs.newrelic.com/docs/kubernetes-pixie/kubernetes-integration/get-started/changes-since-v3#migration-guide" +###### +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrole.yaml new file mode 100644 index 000000000..391dc1e1f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrole.yaml @@ -0,0 +1,35 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }} +rules: + - apiGroups: [""] + resources: + - "nodes/metrics" + - "nodes/stats" + - "nodes/proxy" + verbs: ["get", "list"] + - apiGroups: [ "" ] + resources: + - "endpoints" + - "services" + - "nodes" + - "namespaces" + - "pods" + verbs: [ "get", "list", "watch" ] + - nonResourceURLs: ["/metrics"] + verbs: ["get"] + {{- if .Values.rbac.pspEnabled }} + - apiGroups: + - extensions + resources: + - podsecuritypolicies + resourceNames: + - privileged-{{ include "newrelic.common.naming.fullname" . }} + verbs: + - use + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..fc5dfb8da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl new file mode 100644 index 000000000..320d16dae --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_affinity_helper.tpl @@ -0,0 +1,11 @@ +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 affinity so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.controlPlane.affinity" -}} +{{- if .Values.controlPlane.affinity -}} + {{- toYaml .Values.controlPlane.affinity -}} +{{- else if include "newrelic.common.affinity" . -}} + {{- include "newrelic.common.affinity" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl new file mode 100644 index 000000000..e113def82 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_agent-config_helper.tpl @@ -0,0 +1,20 @@ +{{- /* +Defaults for controlPlane's agent config +*/ -}} +{{- define "nriKubernetes.controlPlane.agentConfig.defaults" -}} +is_forward_only: true +http_server_enabled: true +http_server_port: 8001 +{{- end -}} + + + +{{- define "nriKubernetes.controlPlane.agentConfig" -}} +{{- $agentDefaults := fromYaml ( include "newrelic.common.agentConfig.defaults" . ) -}} +{{- $controlPlane := fromYaml ( include "nriKubernetes.controlPlane.agentConfig.defaults" . ) -}} +{{- $agentConfig := fromYaml ( include "newrelic.compatibility.agentConfig" . ) -}} +{{- $cpAgentConfig := .Values.controlPlane.agentConfig -}} +{{- $customAttributes := dict "custom_attributes" (dict "clusterName" (include "newrelic.common.cluster" . )) -}} + +{{- mustMergeOverwrite $agentDefaults $controlPlane $agentConfig $cpAgentConfig $customAttributes | toYaml -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl new file mode 100644 index 000000000..2f3bdf2d9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_host_network.tpl @@ -0,0 +1,22 @@ +{{/* Returns whether the controlPlane scraper should run with hostNetwork: true based on the user configuration. */}} +{{- define "nriKubernetes.controlPlane.hostNetwork" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values.controlPlane "hostNetwork" | kindIs "bool" -}} + {{- if .Values.controlPlane.hostNetwork -}} + {{- .Values.controlPlane.hostNetwork -}} + {{- end -}} +{{- else if include "newrelic.common.hostNetwork" . -}} + {{- include "newrelic.common.hostNetwork" . -}} +{{- end -}} +{{- end -}} + + + +{{/* Abstraction of "nriKubernetes.controlPlane.hostNetwork" that returns true of false directly */}} +{{- define "nriKubernetes.controlPlane.hostNetwork.value" -}} +{{- if include "nriKubernetes.controlPlane.hostNetwork" . -}} + true +{{- else -}} + false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl new file mode 100644 index 000000000..4b9ef22e3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_naming.tpl @@ -0,0 +1,16 @@ +{{- /* Naming helpers*/ -}} +{{- define "nriKubernetes.controlplane.fullname" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "controlplane") -}} +{{- end -}} + +{{- define "nriKubernetes.controlplane.fullname.agent" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "agent-controlplane") -}} +{{- end -}} + +{{- define "nriKubernetes.controlplane.fullname.serviceAccount" -}} +{{- if include "newrelic.common.serviceAccount.create" . -}} + {{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "controlplane") -}} +{{- else -}} + {{- include "newrelic.common.serviceAccount.name" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl new file mode 100644 index 000000000..a279df6b4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_rbac.tpl @@ -0,0 +1,40 @@ +{{/* +Returns the list of namespaces where secrets need to be accessed by the controlPlane integration to do mTLS Auth +*/}} +{{- define "nriKubernetes.controlPlane.roleBindingNamespaces" -}} +{{ $namespaceList := list }} +{{- range $components := .Values.controlPlane.config }} + {{- if $components }} + {{- if kindIs "map" $components -}} + {{- if $components.staticEndpoint }} + {{- if $components.staticEndpoint.auth }} + {{- if $components.staticEndpoint.auth.mtls }} + {{- if $components.staticEndpoint.auth.mtls.secretNamespace }} + {{- $namespaceList = append $namespaceList $components.staticEndpoint.auth.mtls.secretNamespace -}} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if $components.autodiscover }} + {{- range $autodiscover := $components.autodiscover }} + {{- if $autodiscover }} + {{- if $autodiscover.endpoints }} + {{- range $endpoint := $autodiscover.endpoints }} + {{- if $endpoint.auth }} + {{- if $endpoint.auth.mtls }} + {{- if $endpoint.auth.mtls.secretNamespace }} + {{- $namespaceList = append $namespaceList $endpoint.auth.mtls.secretNamespace -}} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +roleBindingNamespaces: + {{- uniq $namespaceList | toYaml | nindent 2 }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl new file mode 100644 index 000000000..3c82e82f5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/_tolerations_helper.tpl @@ -0,0 +1,11 @@ +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 tolerations so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.controlPlane.tolerations" -}} +{{- if .Values.controlPlane.tolerations -}} + {{- toYaml .Values.controlPlane.tolerations -}} +{{- else if include "newrelic.common.tolerations" . -}} + {{- include "newrelic.common.tolerations" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml new file mode 100644 index 000000000..77f2e11dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/agent-configmap.yaml @@ -0,0 +1,18 @@ +{{- if .Values.controlPlane.enabled -}} +{{- if .Values.customAttributes | kindIs "string" }} +{{- fail ( include "newrelic.compatibility.message.customAttributes" . ) -}} +{{- else -}} +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname.agent" . }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "nriKubernetes.controlPlane.agentConfig" . | nindent 4 }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml new file mode 100644 index 000000000..57633e7f7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrole.yaml @@ -0,0 +1,47 @@ +{{- if and (.Values.controlPlane.enabled) (.Values.rbac.create) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname" . }} +rules: + - apiGroups: [""] + resources: + - "nodes/metrics" + - "nodes/stats" + - "nodes/proxy" + verbs: ["get", "list"] + - apiGroups: [ "" ] + resources: + - "pods" + - "nodes" + verbs: [ "get", "list", "watch" ] + - nonResourceURLs: ["/metrics"] + verbs: ["get", "head"] + {{- if .Values.rbac.pspEnabled }} + - apiGroups: + - extensions + resources: + - podsecuritypolicies + resourceNames: + - privileged-{{ include "newrelic.common.naming.fullname" . }} + verbs: + - use + {{- end -}} +{{- $namespaces := include "nriKubernetes.controlPlane.roleBindingNamespaces" . | fromYaml -}} +{{- if $namespaces.roleBindingNamespaces}} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.naming.secrets" . }} +rules: + - apiGroups: [""] + resources: + - "secrets" + verbs: ["get", "list", "watch"] +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml new file mode 100644 index 000000000..4e3530094 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if and (.Values.controlPlane.enabled) (.Values.rbac.create) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "nriKubernetes.controlplane.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "nriKubernetes.controlplane.fullname.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml new file mode 100644 index 000000000..938fc48d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/daemonset.yaml @@ -0,0 +1,205 @@ +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: apps/v1 +kind: {{ .Values.controlPlane.kind }} +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "nriKubernetes.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname" . }} + {{- $legacyAnnotation:= fromYaml (include "newrelic.compatibility.annotations" .) -}} + {{- with include "newrelic.compatibility.valueWithFallback" (dict "legacy" $legacyAnnotation "supported" .Values.controlPlane.annotations )}} + annotations: {{ . | nindent 4 }} + {{- end }} +spec: + {{- if eq .Values.controlPlane.kind "DaemonSet"}} + {{- with .Values.updateStrategy }} + updateStrategy: {{ toYaml . | nindent 4 }} + {{- end }} + {{- end }} + {{- if eq .Values.controlPlane.kind "Deployment"}} + {{- with .Values.strategy }} + strategy: {{ toYaml . | nindent 4 }} + {{- end }} + {{- end }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: controlplane + template: + metadata: + annotations: + checksum/nri-kubernetes: {{ include (print $.Template.BasePath "/controlplane/scraper-configmap.yaml") . | sha256sum }} + checksum/agent-config: {{ include (print $.Template.BasePath "/controlplane/agent-configmap.yaml") . | sha256sum }} + {{- if include "newrelic.common.license.secret" . }}{{- /* If the is secret to template */}} + checksum/license-secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nriKubernetes.labels.podLabels" . | nindent 8 }} + app.kubernetes.io/component: controlplane + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.images.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + hostNetwork: {{ include "nriKubernetes.controlPlane.hostNetwork.value" . }} + {{- if include "nriKubernetes.controlPlane.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nriKubernetes.controlplane.fullname.serviceAccount" . }} + + {{- if .Values.controlPlane.initContainers }} + initContainers: {{- tpl (.Values.controlPlane.initContainers | toYaml) . | nindent 8 }} + {{- end }} + containers: + - name: controlplane + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} + imagePullPolicy: {{ .Values.images.integration.pullPolicy }} + {{- with include "nriKubernetes.securityContext.container" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: "NRI_KUBERNETES_SINK_HTTP_PORT" + value: {{ get (fromYaml (include "nriKubernetes.controlPlane.agentConfig" .)) "http_server_port" | quote }} + - name: "NRI_KUBERNETES_CLUSTERNAME" + value: {{ include "newrelic.common.cluster" . }} + - name: "NRI_KUBERNETES_VERBOSE" + value: {{ include "newrelic.common.verboseLog.valueAsBoolean" . | quote }} + + - name: "NRI_KUBERNETES_NODENAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + - name: "NRI_KUBERNETES_NODEIP" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "status.hostIP" + + {{- with .Values.controlPlane.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.controlPlane.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: nri-kubernetes-config + mountPath: /etc/newrelic-infra/nri-kubernetes.yml + subPath: nri-kubernetes.yml + {{- with .Values.controlPlane.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.controlPlane.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + - name: forwarder + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.forwarder "context" .) }} + imagePullPolicy: {{ .Values.images.forwarder.pullPolicy }} + {{- with include "nriKubernetes.securityContext.container" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - containerPort: {{ get (fromYaml (include "nriKubernetes.controlPlane.agentConfig" .)) "http_server_port" }} + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + + - name: "NRIA_DNS_HOSTNAME_RESOLUTION" + value: "false" + + - name: "K8S_NODE_NAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + {{- if .Values.useNodeNameAsDisplayName }} + - name: "NRIA_DISPLAY_NAME" + {{- if .Values.prefixDisplayNameWithCluster }} + value: "{{ include "newrelic.common.cluster" . }}:$(K8S_NODE_NAME)" + {{- else }} + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + {{- end }} + {{- end }} + + {{- with .Values.controlPlane.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.controlPlane.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: /var/db/newrelic-infra/data + name: forwarder-tmpfs-data + - mountPath: /var/db/newrelic-infra/user_data + name: forwarder-tmpfs-user-data + - mountPath: /tmp + name: forwarder-tmpfs-tmp + - name: config + mountPath: /etc/newrelic-infra.yml + subPath: newrelic-infra.yml + {{- with .Values.controlPlane.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.controlPlane.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: nri-kubernetes-config + configMap: + name: {{ include "nriKubernetes.controlplane.fullname" . }} + items: + - key: nri-kubernetes.yml + path: nri-kubernetes.yml + - name: forwarder-tmpfs-data + emptyDir: {} + - name: forwarder-tmpfs-user-data + emptyDir: {} + - name: forwarder-tmpfs-tmp + emptyDir: {} + - name: config + configMap: + name: {{ include "nriKubernetes.controlplane.fullname.agent" . }} + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + {{- with .Values.controlPlane.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.controlPlane.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.controlPlane.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{- with .Values.controlPlane.nodeSelector | default (fromYaml (include "newrelic.common.nodeSelector" .)) }} + {{- toYaml . | nindent 8 }} + {{- end -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml new file mode 100644 index 000000000..d97fc181a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if .Values.rbac.create }} +{{- $namespaces := (include "nriKubernetes.controlPlane.roleBindingNamespaces" . | fromYaml) -}} +{{- range $namespaces.roleBindingNamespaces }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + {{- include "newrelic.common.labels" $ | nindent 4 }} + name: {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" $) "suffix" .) }} + namespace: {{ . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "nriKubernetes.naming.secrets" $ }} +subjects: +- kind: ServiceAccount + name: {{ include "nriKubernetes.controlplane.fullname.serviceAccount" $ }} + namespace: {{ $.Release.Namespace }} +{{- end -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml new file mode 100644 index 000000000..454665ded --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/scraper-configmap.yaml @@ -0,0 +1,36 @@ +{{- if .Values.controlPlane.enabled -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname" . }} + namespace: {{ .Release.Namespace }} +data: + nri-kubernetes.yml: |- + {{- (merge .Values.common.config (include "newrelic.integrationConfigDefaults" . | fromYaml)) | toYaml | nindent 4 }} + controlPlane: + {{- omit .Values.controlPlane.config "etcd" "scheduler" "controllerManager" "apiServer" | toYaml | nindent 6 }} + enabled: true + + {{- if .Values.controlPlane.config.etcd.enabled }} + etcd: + {{- toYaml .Values.controlPlane.config.etcd | nindent 8 -}} + {{- end -}} + + {{- if .Values.controlPlane.config.scheduler.enabled }} + scheduler: + {{- toYaml .Values.controlPlane.config.scheduler | nindent 8 -}} + {{- end -}} + + {{- if .Values.controlPlane.config.controllerManager.enabled }} + controllerManager: + {{- toYaml .Values.controlPlane.config.controllerManager | nindent 8 -}} + {{- end -}} + + {{- if .Values.controlPlane.config.apiServer.enabled }} + apiServer: + {{- toYaml .Values.controlPlane.config.apiServer | nindent 8 -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml new file mode 100644 index 000000000..502e1c986 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/controlplane/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.controlplane.fullname.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl new file mode 100644 index 000000000..ce795708d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_affinity_helper.tpl @@ -0,0 +1,14 @@ +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 affinity so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.ksm.affinity" -}} +{{- if or .Values.ksm.affinity .Values.nodeAffinity -}} + {{- $legacyNodeAffinity := fromYaml ( include "newrelic.compatibility.nodeAffinity" . ) | default dict -}} + {{- $valuesAffinity := .Values.ksm.affinity | default dict -}} + {{- $affinity := mustMergeOverwrite $legacyNodeAffinity $valuesAffinity -}} + {{- toYaml $affinity -}} +{{- else if include "newrelic.common.affinity" . -}} + {{- include "newrelic.common.affinity" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl new file mode 100644 index 000000000..e7b55644c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_agent-config_helper.tpl @@ -0,0 +1,20 @@ +{{- /* +Defaults for ksm's agent config +*/ -}} +{{- define "nriKubernetes.ksm.agentConfig.defaults" -}} +is_forward_only: true +http_server_enabled: true +http_server_port: 8002 +{{- end -}} + + + +{{- define "nriKubernetes.ksm.agentConfig" -}} +{{- $agentDefaults := fromYaml ( include "newrelic.common.agentConfig.defaults" . ) -}} +{{- $ksm := fromYaml ( include "nriKubernetes.ksm.agentConfig.defaults" . ) -}} +{{- $agentConfig := fromYaml ( include "newrelic.compatibility.agentConfig" . ) -}} +{{- $ksmAgentConfig := .Values.ksm.agentConfig -}} +{{- $customAttributes := dict "custom_attributes" (dict "clusterName" (include "newrelic.common.cluster" . )) -}} + +{{- mustMergeOverwrite $agentDefaults $ksm $agentConfig $ksmAgentConfig $customAttributes | toYaml -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl new file mode 100644 index 000000000..59a6db7be --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_host_network.tpl @@ -0,0 +1,22 @@ +{{/* Returns whether the ksm scraper should run with hostNetwork: true based on the user configuration. */}} +{{- define "nriKubernetes.ksm.hostNetwork" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values.ksm "hostNetwork" | kindIs "bool" -}} + {{- if .Values.ksm.hostNetwork -}} + {{- .Values.ksm.hostNetwork -}} + {{- end -}} +{{- else if include "newrelic.common.hostNetwork" . -}} + {{- include "newrelic.common.hostNetwork" . -}} +{{- end -}} +{{- end -}} + + + +{{/* Abstraction of "nriKubernetes.ksm.hostNetwork" that returns true of false directly */}} +{{- define "nriKubernetes.ksm.hostNetwork.value" -}} +{{- if include "nriKubernetes.ksm.hostNetwork" . -}} + true +{{- else -}} + false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_naming.tpl new file mode 100644 index 000000000..d8c283c43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_naming.tpl @@ -0,0 +1,8 @@ +{{- /* Naming helpers*/ -}} +{{- define "nriKubernetes.ksm.fullname" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "ksm") -}} +{{- end -}} + +{{- define "nriKubernetes.ksm.fullname.agent" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "agent-ksm") -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl new file mode 100644 index 000000000..e1a9fd80c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/_tolerations_helper.tpl @@ -0,0 +1,11 @@ +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 tolerations so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.ksm.tolerations" -}} +{{- if .Values.ksm.tolerations -}} + {{- toYaml .Values.ksm.tolerations -}} +{{- else if include "newrelic.common.tolerations" . -}} + {{- include "newrelic.common.tolerations" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml new file mode 100644 index 000000000..6a438e9a3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/agent-configmap.yaml @@ -0,0 +1,18 @@ +{{- if .Values.ksm.enabled -}} +{{- if .Values.customAttributes | kindIs "string" }} +{{- fail ( include "newrelic.compatibility.message.customAttributes" . ) -}} +{{- else -}} +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.ksm.fullname.agent" . }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "nriKubernetes.ksm.agentConfig" . | nindent 4 }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/deployment.yaml new file mode 100644 index 000000000..507199d5a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/deployment.yaml @@ -0,0 +1,192 @@ +{{- if include "newrelic.compatibility.ksm.enabled" . -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "nriKubernetes.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.ksm.fullname" . }} + {{- $legacyAnnotation:= fromYaml (include "newrelic.compatibility.annotations" .) -}} + {{- with include "newrelic.compatibility.valueWithFallback" (dict "legacy" $legacyAnnotation "supported" .Values.ksm.annotations )}} + annotations: {{ . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.strategy }} + strategy: {{ toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: ksm + template: + metadata: + annotations: + checksum/nri-kubernetes: {{ include (print $.Template.BasePath "/ksm/scraper-configmap.yaml") . | sha256sum }} + checksum/agent-config: {{ include (print $.Template.BasePath "/ksm/agent-configmap.yaml") . | sha256sum }} + {{- if include "newrelic.common.license.secret" . }}{{- /* If the is secret to template */}} + checksum/license-secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nriKubernetes.labels.podLabels" . | nindent 8 }} + app.kubernetes.io/component: ksm + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.images.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + hostNetwork: {{ include "nriKubernetes.ksm.hostNetwork.value" . }} + {{- if include "nriKubernetes.ksm.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + + {{- if .Values.ksm.initContainers }} + initContainers: {{- tpl (.Values.ksm.initContainers | toYaml) . | nindent 8 }} + {{- end }} + containers: + - name: ksm + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} + imagePullPolicy: {{ .Values.images.integration.pullPolicy }} + {{- with include "nriKubernetes.securityContext.container" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: "NRI_KUBERNETES_SINK_HTTP_PORT" + value: {{ get (fromYaml (include "nriKubernetes.ksm.agentConfig" .)) "http_server_port" | quote }} + - name: "NRI_KUBERNETES_CLUSTERNAME" + value: {{ include "newrelic.common.cluster" . }} + - name: "NRI_KUBERNETES_VERBOSE" + value: {{ include "newrelic.common.verboseLog.valueAsBoolean" . | quote }} + + - name: "NRI_KUBERNETES_NODENAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + {{- with .Values.ksm.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ksm.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: nri-kubernetes-config + mountPath: /etc/newrelic-infra/nri-kubernetes.yml + subPath: nri-kubernetes.yml + {{- with .Values.ksm.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ksm.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + - name: forwarder + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.forwarder "context" .) }} + imagePullPolicy: {{ .Values.images.forwarder.pullPolicy }} + {{- with include "nriKubernetes.securityContext.container" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - containerPort: {{ get (fromYaml (include "nriKubernetes.ksm.agentConfig" .)) "http_server_port" }} + env: + - name: NRIA_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + + - name: "NRIA_DNS_HOSTNAME_RESOLUTION" + value: "false" + + - name: "K8S_NODE_NAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + {{- if .Values.useNodeNameAsDisplayName }} + - name: "NRIA_DISPLAY_NAME" + {{- if .Values.prefixDisplayNameWithCluster }} + value: "{{ include "newrelic.common.cluster" . }}:$(K8S_NODE_NAME)" + {{- else }} + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + {{- end }} + {{- end }} + + {{- with .Values.ksm.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ksm.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: /var/db/newrelic-infra/data + name: forwarder-tmpfs-data + - mountPath: /var/db/newrelic-infra/user_data + name: forwarder-tmpfs-user-data + - mountPath: /tmp + name: forwarder-tmpfs-tmp + - name: config + mountPath: /etc/newrelic-infra.yml + subPath: newrelic-infra.yml + {{- with .Values.ksm.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ksm.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: nri-kubernetes-config + configMap: + name: {{ include "nriKubernetes.ksm.fullname" . }} + items: + - key: nri-kubernetes.yml + path: nri-kubernetes.yml + - name: forwarder-tmpfs-data + emptyDir: {} + - name: forwarder-tmpfs-user-data + emptyDir: {} + - name: forwarder-tmpfs-tmp + emptyDir: {} + - name: config + configMap: + name: {{ include "nriKubernetes.ksm.fullname.agent" . }} + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + {{- with .Values.ksm.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.ksm.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.ksm.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{- with .Values.ksm.nodeSelector | default (fromYaml (include "newrelic.common.nodeSelector" .)) }} + {{- toYaml . | nindent 8 }} + {{- end -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml new file mode 100644 index 000000000..3314df9c7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/ksm/scraper-configmap.yaml @@ -0,0 +1,15 @@ +{{- if include "newrelic.compatibility.ksm.enabled" . -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.ksm.fullname" . }} + namespace: {{ .Release.Namespace }} +data: + nri-kubernetes.yml: |- + {{- (merge .Values.common.config (include "newrelic.integrationConfigDefaults" . | fromYaml)) | toYaml | nindent 4 }} + ksm: + {{- mustMergeOverwrite .Values.ksm.config (include "newrelic.compatibility.ksm.legacyData" . | fromYaml) | toYaml | nindent 6 -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl new file mode 100644 index 000000000..a3abf0855 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_affinity_helper.tpl @@ -0,0 +1,33 @@ +{{- /* +Patch to add affinity in case we are running in fargate mode +*/ -}} +{{- define "nriKubernetes.kubelet.affinity.fargateDefaults" -}} +nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: eks.amazonaws.com/compute-type + operator: NotIn + values: + - fargate +{{- end -}} + + + +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 affinity so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.kubelet.affinity" -}} + +{{- if or .Values.kubelet.affinity .Values.nodeAffinity -}} + {{- $legacyNodeAffinity := fromYaml ( include "newrelic.compatibility.nodeAffinity" . ) | default dict -}} + {{- $valuesAffinity := .Values.kubelet.affinity | default dict -}} + {{- $affinity := mustMergeOverwrite $legacyNodeAffinity $valuesAffinity -}} + {{- toYaml $affinity -}} +{{- else if include "newrelic.common.affinity" . -}} + {{- include "newrelic.common.affinity" . -}} +{{- else if include "newrelic.fargate" . -}} + {{- include "nriKubernetes.kubelet.affinity.fargateDefaults" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl new file mode 100644 index 000000000..ea6ffc25f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_agent-config_helper.tpl @@ -0,0 +1,31 @@ +{{- /* +Defaults for kubelet's agent config +*/ -}} +{{- define "nriKubernetes.kubelet.agentConfig.defaults" -}} +http_server_enabled: true +http_server_port: 8003 +features: + docker_enabled: false +{{- if not ( include "newrelic.common.privileged" . ) }} +is_secure_forward_only: true +{{- end }} +{{- /* +`enableProcessMetrics` is commented in the values and we want to configure it when it is set to something +either `true` or `false`. So we test if the variable is a boolean and in that case simply use it. +*/}} +{{- if (get .Values "enableProcessMetrics" | kindIs "bool") }} +enable_process_metrics: {{ .Values.enableProcessMetrics }} +{{- end }} +{{- end -}} + + + +{{- define "nriKubernetes.kubelet.agentConfig" -}} +{{- $agentDefaults := fromYaml ( include "newrelic.common.agentConfig.defaults" . ) -}} +{{- $kubelet := fromYaml ( include "nriKubernetes.kubelet.agentConfig.defaults" . ) -}} +{{- $agentConfig := fromYaml ( include "newrelic.compatibility.agentConfig" . ) -}} +{{- $kubeletAgentConfig := .Values.kubelet.agentConfig -}} +{{- $customAttributes := dict "custom_attributes" (dict "clusterName" (include "newrelic.common.cluster" . )) -}} + +{{- mustMergeOverwrite $agentDefaults $kubelet $agentConfig $kubeletAgentConfig $customAttributes | toYaml -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl new file mode 100644 index 000000000..7944f98a7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_host_network.tpl @@ -0,0 +1,22 @@ +{{/* Returns whether the kubelet scraper should run with hostNetwork: true based on the user configuration. */}} +{{- define "nriKubernetes.kubelet.hostNetwork" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values.kubelet "hostNetwork" | kindIs "bool" -}} + {{- if .Values.kubelet.hostNetwork -}} + {{- .Values.kubelet.hostNetwork -}} + {{- end -}} +{{- else if include "newrelic.common.hostNetwork" . -}} + {{- include "newrelic.common.hostNetwork" . -}} +{{- end -}} +{{- end -}} + + + +{{/* Abstraction of "nriKubernetes.kubelet.hostNetwork" that returns true of false directly */}} +{{- define "nriKubernetes.kubelet.hostNetwork.value" -}} +{{- if include "nriKubernetes.kubelet.hostNetwork" . -}} + true +{{- else -}} + false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl new file mode 100644 index 000000000..71c142156 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_naming.tpl @@ -0,0 +1,12 @@ +{{- /* Naming helpers*/ -}} +{{- define "nriKubernetes.kubelet.fullname" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "kubelet") -}} +{{- end -}} + +{{- define "nriKubernetes.kubelet.fullname.agent" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "agent-kubelet") -}} +{{- end -}} + +{{- define "nriKubernetes.kubelet.fullname.integrations" -}} +{{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "nriKubernetes.naming.fullname" .) "suffix" "integrations-cfg") -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl new file mode 100644 index 000000000..4e334466c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_security_context_helper.tpl @@ -0,0 +1,32 @@ +{{- /*This defines the defaults that the privileged mode has for the agent's securityContext */ -}} +{{- define "nriKubernetes.kubelet.securityContext.privileged" -}} +runAsUser: 0 +runAsGroup: 0 +allowPrivilegeEscalation: true +privileged: true +readOnlyRootFilesystem: true +{{- end -}} + + + +{{- /* This is the container security context for the agent */ -}} +{{- define "nriKubernetes.kubelet.securityContext.agentContainer" -}} +{{- $defaults := dict -}} +{{- if include "newrelic.common.privileged" . -}} +{{- $defaults = fromYaml ( include "nriKubernetes.kubelet.securityContext.privileged" . ) -}} +{{- else -}} +{{- $defaults = fromYaml ( include "nriKubernetes.securityContext.containerDefaults" . ) -}} +{{- end -}} + +{{- $compatibilityLayer := include "newrelic.compatibility.securityContext" . | fromYaml -}} +{{- $commonLibrary := include "newrelic.common.securityContext.container" . | fromYaml -}} + +{{- $finalSecurityContext := dict -}} +{{- if $commonLibrary -}} + {{- $finalSecurityContext = mustMergeOverwrite $commonLibrary $compatibilityLayer -}} +{{- else -}} + {{- $finalSecurityContext = mustMergeOverwrite $defaults $compatibilityLayer -}} +{{- end -}} + +{{- toYaml $finalSecurityContext -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl new file mode 100644 index 000000000..e46d83d69 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/_tolerations_helper.tpl @@ -0,0 +1,11 @@ +{{- /* +As this chart deploys what it should be three charts to maintain the transition to v3 as smooth as possible. +This means that this chart has 3 tolerations so a helper should be done per scraper. +*/ -}} +{{- define "nriKubernetes.kubelet.tolerations" -}} +{{- if .Values.kubelet.tolerations -}} + {{- toYaml .Values.kubelet.tolerations -}} +{{- else if include "newrelic.common.tolerations" . -}} + {{- include "newrelic.common.tolerations" . -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml new file mode 100644 index 000000000..0f71f129a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/agent-configmap.yaml @@ -0,0 +1,18 @@ +{{- if .Values.kubelet.enabled -}} +{{- if .Values.customAttributes | kindIs "string" }} +{{- fail ( include "newrelic.compatibility.message.customAttributes" . ) -}} +{{- else -}} +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.kubelet.fullname.agent" . }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "nriKubernetes.kubelet.agentConfig" . | nindent 4 }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml new file mode 100644 index 000000000..517079be7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/daemonset.yaml @@ -0,0 +1,265 @@ +{{- if (.Values.kubelet.enabled) }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "nriKubernetes.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.kubelet.fullname" . }} + {{- $legacyAnnotation:= fromYaml (include "newrelic.compatibility.annotations" .) -}} + {{- with include "newrelic.compatibility.valueWithFallback" (dict "legacy" $legacyAnnotation "supported" .Values.kubelet.annotations )}} + annotations: {{ . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.updateStrategy }} + updateStrategy: {{ toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: kubelet + template: + metadata: + annotations: + checksum/nri-kubernetes: {{ include (print $.Template.BasePath "/kubelet/scraper-configmap.yaml") . | sha256sum }} + checksum/agent-config: {{ include (print $.Template.BasePath "/kubelet/agent-configmap.yaml") . | sha256sum }} + {{- if include "newrelic.common.license.secret" . }}{{- /* If the is secret to template */}} + checksum/license-secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + checksum/integrations_config: {{ include (print $.Template.BasePath "/kubelet/integrations-configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nriKubernetes.labels.podLabels" . | nindent 8 }} + app.kubernetes.io/component: kubelet + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.images.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + hostNetwork: {{ include "nriKubernetes.kubelet.hostNetwork.value" . }} + {{- if include "nriKubernetes.kubelet.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + + {{- if .Values.kubelet.initContainers }} + initContainers: {{- tpl (.Values.kubelet.initContainers | toYaml) . | nindent 8 }} + {{- end }} + containers: + - name: kubelet + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} + imagePullPolicy: {{ .Values.images.integration.pullPolicy }} + {{- with include "nriKubernetes.securityContext.container" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: "NRI_KUBERNETES_SINK_HTTP_PORT" + value: {{ get (fromYaml (include "nriKubernetes.kubelet.agentConfig" .)) "http_server_port" | quote }} + - name: "NRI_KUBERNETES_CLUSTERNAME" + value: {{ include "newrelic.common.cluster" . }} + - name: "NRI_KUBERNETES_VERBOSE" + value: {{ include "newrelic.common.verboseLog.valueAsBoolean" . | quote }} + + - name: "NRI_KUBERNETES_NODENAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + # Required to connect to the kubelet + - name: "NRI_KUBERNETES_NODEIP" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "status.hostIP" + + {{- with .Values.kubelet.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.kubelet.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: nri-kubernetes-config + mountPath: /etc/newrelic-infra/nri-kubernetes.yml + subPath: nri-kubernetes.yml + {{- with .Values.kubelet.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.kubelet.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + - name: agent + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} + args: [ "newrelic-infra" ] + imagePullPolicy: {{ .Values.images.agent.pullPolicy }} + {{- with include "nriKubernetes.kubelet.securityContext.agentContainer" . | fromYaml }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - containerPort: {{ get (fromYaml (include "nriKubernetes.kubelet.agentConfig" .)) "http_server_port" }} + env: + - name: NRIA_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + + - name: "NRIA_OVERRIDE_HOSTNAME_SHORT" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + - name: "NRIA_OVERRIDE_HOSTNAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + {{- if not (include "newrelic.common.privileged" .) }} + # Override NRIA_OVERRIDE_HOST_ROOT to empty if unprivileged. This must be done as an env var as the + # `k8s-events-forwarder` and `infrastructure-bundle` images ship this very same env var set to /host. + - name: "NRIA_OVERRIDE_HOST_ROOT" + value: "" + {{- end }} + + - name: "NRI_KUBERNETES_NODE_NAME" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + + {{- if .Values.useNodeNameAsDisplayName }} + - name: "NRIA_DISPLAY_NAME" + {{- if .Values.prefixDisplayNameWithCluster }} + value: "{{ include "newrelic.common.cluster" . }}:$(NRI_KUBERNETES_NODE_NAME)" + {{- else }} + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "spec.nodeName" + {{- end }} + {{- end }} + + {{- /* Needed to populate clustername in integration metrics */}} + - name: "CLUSTER_NAME" + value: {{ include "newrelic.common.cluster" . }} + - name: "NRIA_PASSTHROUGH_ENVIRONMENT" + value: "CLUSTER_NAME" + + {{- /* Needed for autodiscovery since hostNetwork=false */}} + - name: "NRIA_HOST" + valueFrom: + fieldRef: + apiVersion: "v1" + fieldPath: "status.hostIP" + + {{- with .Values.kubelet.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.kubelet.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/newrelic-infra.yml + subPath: newrelic-infra.yml + - name: nri-integrations-cfg-volume + mountPath: /etc/newrelic-infra/integrations.d/ + {{- if include "newrelic.common.privileged" . }} + - name: dev + mountPath: /dev + - name: host-containerd-socket + mountPath: /run/containerd/containerd.sock + - name: host-docker-socket + mountPath: /var/run/docker.sock + - name: log + mountPath: /var/log + - name: host-volume + mountPath: /host + mountPropagation: HostToContainer + readOnly: true + {{- end }} + - mountPath: /var/db/newrelic-infra/data + name: agent-tmpfs-data + - mountPath: /var/db/newrelic-infra/user_data + name: agent-tmpfs-user-data + - mountPath: /tmp + name: agent-tmpfs-tmp + {{- with .Values.kubelet.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.kubelet.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + volumes: + {{- if include "newrelic.common.privileged" . }} + - name: dev + hostPath: + path: /dev + - name: host-containerd-socket + hostPath: + path: /run/containerd/containerd.sock + - name: host-docker-socket + hostPath: + path: /var/run/docker.sock + - name: log + hostPath: + path: /var/log + - name: host-volume + hostPath: + path: / + {{- end }} + - name: agent-tmpfs-data + emptyDir: {} + - name: agent-tmpfs-user-data + emptyDir: {} + - name: agent-tmpfs-tmp + emptyDir: {} + - name: nri-kubernetes-config + configMap: + name: {{ include "nriKubernetes.kubelet.fullname" . }} + items: + - key: nri-kubernetes.yml + path: nri-kubernetes.yml + - name: config + configMap: + name: {{ include "nriKubernetes.kubelet.fullname.agent" . }} + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + - name: nri-integrations-cfg-volume + configMap: + name: {{ include "nriKubernetes.kubelet.fullname.integrations" . }} + {{- with .Values.kubelet.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.kubelet.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + {{- with include "nriKubernetes.kubelet.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{- with .Values.kubelet.nodeSelector | default (fromYaml (include "newrelic.common.nodeSelector" .)) }} + {{- toYaml . | nindent 8 }} + {{- end -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml new file mode 100644 index 000000000..abf381f38 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/integrations-configmap.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.kubelet.fullname.integrations" . }} +data: + # This ConfigMap holds config files for integrations. They should have the following format: + #redis-config.yml: | + # # Run auto discovery to find pods with label "app=redis" + # discovery: + # command: + # # Run discovery for Kubernetes. Use the following optional arguments: + # # --namespaces: Comma separated list of namespaces to discover pods on + # # --tls: Use secure (TLS) connection + # # --port: Port used to connect to the kubelet. Default is 10255 + # exec: /var/db/newrelic-infra/nri-discovery-kubernetes --port PORT --tls + # match: + # label.app: redis + # integrations: + # - name: nri-redis + # env: + # # using the discovered IP as the hostname address + # HOSTNAME: ${discovery.ip} + # PORT: 6379 + # KEYS: '{"0":[""],"1":[""]}' + # REMOTE_MONITORING: true + # labels: + # env: production + {{- if .Values.integrations -}} + {{- range $k, $v := .Values.integrations -}} + {{- $k | trimSuffix ".yaml" | trimSuffix ".yml" | nindent 2 -}}.yaml: |- + {{- $v | toYaml | nindent 4 -}} + {{- end }} + {{- end }} + + {{- /* This template will add and template the integrations in the old .Values.integrations_config */}} + {{- include "newrelic.compatibility.integrations" . | nindent 2 }} + + {{- /* This template will add Pixie Health check to the integrations */}} + {{- if .Values.selfMonitoring.pixie.enabled }} + pixie-health-check.yaml: | + --- + # This Flex config performs periodic checks of the Pixie + # /healthz and /statusz endpoints exposed by the Pixie Cloud Connector. + # A status for each endpoint is sent to New Relic in a pixieHealthCheck event. + # + # If Pixie is not installed in the cluster, no events will be generated. + # This can also be disabled with enablePixieHealthCheck: false in the values.yaml file. + discovery: + command: + exec: /var/db/newrelic-infra/nri-discovery-kubernetes --tls --port 10250 + match: + label.name: vizier-cloud-connector + integrations: + - name: nri-flex + interval: 60s + config: + name: pixie-health-check + apis: + - event_type: pixieHealth + commands: + - run: curl --insecure -s https://${discovery.ip}:50800/healthz | xargs | awk '{print "cloud_connector_health:"$1}' + split_by: ":" + merge: pixieHealthCheck + - event_type: pixieStatus + commands: + - run: curl --insecure -s https://${discovery.ip}:50800/statusz | awk '{if($1 == ""){ print "cloud_connector_status:OK" } else { print "cloud_connector_status:"$1 }}' + split_by: ":" + merge: pixieHealthCheck + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml new file mode 100644 index 000000000..e43b5227f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/kubelet/scraper-configmap.yaml @@ -0,0 +1,18 @@ +{{- if .Values.kubelet.enabled -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "nriKubernetes.kubelet.fullname" . }} + namespace: {{ .Release.Namespace }} +data: + nri-kubernetes.yml: | + {{- (merge .Values.common.config (include "newrelic.integrationConfigDefaults" . | fromYaml)) | toYaml | nindent 4 }} + kubelet: + enabled: true + {{- if .Values.kubelet.config }} + {{- toYaml .Values.kubelet.config | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..5b5058511 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/podsecuritypolicy.yaml @@ -0,0 +1,26 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: privileged-{{ include "newrelic.common.naming.fullname" . }} +spec: + allowedCapabilities: + - '*' + fsGroup: + rule: RunAsAny + privileged: true + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - '*' + hostPID: true + hostIPC: true + hostNetwork: true + hostPorts: + - min: 1 + max: 65536 +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/secret.yaml @@ -0,0 +1,2 @@ +{{- /* Common library will take care of creating the secret or not. */}} +{{- include "newrelic.common.license.secret" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/serviceaccount.yaml new file mode 100644 index 000000000..f987cc512 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/values.yaml new file mode 100644 index 000000000..fbbf3ca79 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-infrastructure/values.yaml @@ -0,0 +1,599 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` +cluster: "" +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" +# -- In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` +customSecretName: "" +# -- In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` +customSecretLicenseKey: "" + +# -- Images used by the chart for the integration and agents. +# @default -- See `values.yaml` +images: + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + # -- Image for the New Relic Infrastructure Agent sidecar. + # @default -- See `values.yaml` + forwarder: + registry: "" + repository: newrelic/k8s-events-forwarder + tag: 1.57.2 + pullPolicy: IfNotPresent + # -- Image for the New Relic Infrastructure Agent plus integrations. + # @default -- See `values.yaml` + agent: + registry: "" + repository: newrelic/infrastructure-bundle + tag: 3.2.58 + pullPolicy: IfNotPresent + # -- Image for the New Relic Kubernetes integration. + # @default -- See `values.yaml` + integration: + registry: "" + repository: newrelic/nri-kubernetes + tag: + pullPolicy: IfNotPresent + +# -- Config that applies to all instances of the solution: kubelet, ksm, control plane and sidecars. +# @default -- See `values.yaml` +common: + # Configuration entries that apply to all instances of the integration: kubelet, ksm and control plane. + config: + # common.config.interval -- (duration) Intervals larger than 40s are not supported and will cause the NR UI to not + # behave properly. Any non-nil value will override the `lowDataMode` default. + # @default -- `15s` (See [Low data mode](README.md#low-data-mode)) + interval: + # -- Config for filtering ksm and kubelet metrics by namespace. + namespaceSelector: {} + # If you want to include only namespaces with a given label you could do so by adding: + # matchLabels: + # newrelic.com/scrape: true + # Otherwise you can build more complex filters and include or exclude certain namespaces by adding one or multiple + # expressions that are added, for instance: + # matchExpressions: + # - {key: newrelic.com/scrape, operator: NotIn, values: ["false"]} + + # -- Config for the Infrastructure agent. + # Will be used by the forwarder sidecars and the agent running integrations. + # See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + agentConfig: {} + +# lowDataMode -- (bool) Send less data by incrementing the interval from `15s` (the default when `lowDataMode` is `false` or `nil`) to `30s`. +# Non-nil values of `common.config.interval` will override this value. +# @default -- `false` (See [Low data mode](README.md#low-data-mode)) +lowDataMode: + +# sink - Configuration for the scraper sink. +sink: + http: + # -- The amount of time the scraper container to probe infra agent sidecar container before giving up and restarting during pod starts. + probeTimeout: 90s + # -- The amount of time the scraper container to backoff when it fails to probe infra agent sidecar. + probeBackoff: 5s + +# kubelet -- Configuration for the DaemonSet that collects metrics from the Kubelet. +# @default -- See `values.yaml` +kubelet: + # -- Enable kubelet monitoring. + # Advanced users only. Setting this to `false` is not supported and will break the New Relic experience. + enabled: true + annotations: {} + # -- Tolerations for the control plane DaemonSet. + # @default -- Schedules in all tainted nodes + tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + nodeSelector: {} + # -- (bool) Sets pod's hostNetwork. When set bypasses global/common variable + # @default -- Not set + hostNetwork: + affinity: {} + # -- Config for the Infrastructure agent that will forward the metrics to the backend and will run the integrations in this cluster. + # It will be merged with the configuration in `.common.agentConfig`. You can see all the agent configurations in + # [New Relic docs](https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/) + # e.g. you can set `passthrough_environment` int the [config file](https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/configure-infrastructure-agent/#config-file) + # so the agent let use that environment variables to the integrations. + agentConfig: {} + # passthrough_environment: + # - A_ENVIRONMENT_VARIABLE_SET_IN_extraEnv + # - A_ENVIRONMENT_VARIABLE_SET_IN_A_CONFIG_MAP_SET_IN_entraEnvForm + + # -- Add user environment variables to the agent + extraEnv: [] + # -- Add user environment from configMaps or secrets as variables to the agent + extraEnvFrom: [] + # -- Volumes to mount in the containers + extraVolumes: [] + # -- Defines where to mount volumes specified with `extraVolumes` + extraVolumeMounts: [] + initContainers: [] + resources: + limits: + memory: 300M + requests: + cpu: 100m + memory: 150M + config: + # -- Timeout for the kubelet APIs contacted by the integration + timeout: 10s + # -- Number of retries after timeout expired + retries: 3 + # -- Max number of scraper rerun when scraper runtime error happens + scraperMaxReruns: 4 + # port: + # scheme: + +# ksm -- Configuration for the Deployment that collects state metrics from KSM (kube-state-metrics). +# @default -- See `values.yaml` +ksm: + # -- Enable cluster state monitoring. + # Advanced users only. Setting this to `false` is not supported and will break the New Relic experience. + enabled: true + annotations: {} + # -- Tolerations for the KSM Deployment. + # @default -- Schedules in all tainted nodes + tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + nodeSelector: {} + # -- (bool) Sets pod's hostNetwork. When set bypasses global/common variable + # @default -- Not set + hostNetwork: + # -- Affinity for the KSM Deployment. + # @default -- Deployed in the same node as KSM + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app.kubernetes.io/name: kube-state-metrics + weight: 100 + # -- Config for the Infrastructure agent that will forward the metrics to the backend. It will be merged with the configuration in `.common.agentConfig` + # See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + agentConfig: {} + extraEnv: [] + extraEnvFrom: [] + extraVolumes: [] + extraVolumeMounts: [] + initContainers: [] + # -- Resources for the KSM scraper pod. + # Keep in mind that sharding is not supported at the moment, so memory usage for this component ramps up quickly on + # large clusters. + # @default -- 100m/150M -/850M + resources: + limits: + memory: 850M # Bump me up if KSM pod shows restarts. + requests: + cpu: 100m + memory: 150M + config: + # -- Timeout for the ksm API contacted by the integration + timeout: 10s + # -- Number of retries after timeout expired + retries: 3 + # -- if specified autodiscovery is not performed and the specified URL is used + # staticUrl: "http://test.io:8080/metrics" + # -- Label selector that will be used to automatically discover an instance of kube-state-metrics running in the cluster. + selector: "app.kubernetes.io/name=kube-state-metrics" + # -- Scheme to use to connect to kube-state-metrics. Supported values are `http` and `https`. + scheme: "http" + # -- Restrict autodiscovery of the kube-state-metrics endpoint to those using a specific port. If empty or `0`, all endpoints are considered regardless of their port (recommended). + # port: 8080 + # -- Restrict autodiscovery of the kube-state-metrics service to a particular namespace. + # @default -- All namespaces are searched (recommended). + # namespace: "ksm-namespace" + +# controlPlane -- Configuration for the control plane scraper. +# @default -- See `values.yaml` +controlPlane: + # -- Deploy control plane monitoring component. + enabled: true + annotations: {} + # -- Tolerations for the control plane DaemonSet. + # @default -- Schedules in all tainted nodes + tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + nodeSelector: {} + # -- Affinity for the control plane DaemonSet. + # @default -- Deployed only in control plane nodes. + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + - matchExpressions: + - key: node-role.kubernetes.io/controlplane + operator: Exists + - matchExpressions: + - key: node-role.kubernetes.io/etcd + operator: Exists + # -- How to deploy the control plane scraper. If autodiscovery is in use, it should be `DaemonSet`. + # Advanced users using static endpoints set this to `Deployment` to avoid reporting metrics twice. + kind: DaemonSet + # -- Run Control Plane scraper with `hostNetwork`. + # `hostNetwork` is required for most control plane configurations, as they only accept connections from localhost. + hostNetwork: true + # -- Config for the Infrastructure agent that will forward the metrics to the backend. It will be merged with the configuration in `.common.agentConfig` + # See: https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + agentConfig: {} + extraEnv: [] + extraEnvFrom: [] + extraVolumes: [] + extraVolumeMounts: [] + initContainers: [] + resources: + limits: + memory: 300M + requests: + cpu: 100m + memory: 150M + config: + # -- Timeout for the Kubernetes APIs contacted by the integration + timeout: 10s + # -- Number of retries after timeout expired + retries: 3 + # -- etcd monitoring configuration + # @default -- Common settings for most K8s distributions. + etcd: + # -- Enable etcd monitoring. Might require manual configuration in some environments. + enabled: true + # Discover etcd pods using the following namespaces and selectors. + # If a pod matches the selectors, the scraper will attempt to reach it through the `endpoints` defined below. + autodiscover: + - selector: "tier=control-plane,component=etcd" + namespace: kube-system + # Set to true to consider only pods sharing the node with the scraper pod. + # This should be set to `true` if Kind is Daemonset, `false` otherwise. + matchNode: true + # Try to reach etcd using the following endpoints. + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + - url: http://localhost:2381 + - selector: "k8s-app=etcd-manager-main" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + - selector: "k8s-app=etcd" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:4001 + insecureSkipVerify: true + auth: + type: bearer + # Openshift users might want to remove previous autodiscover entries and add this one instead. + # Manual steps are required to create a secret containing the required TLS certificates to connect to etcd. + # - selector: "app=etcd,etcd=true,k8s-app=etcd" + # namespace: openshift-etcd + # matchNode: true + # endpoints: + # - url: https://localhost:9979 + # insecureSkipVerify: true + # auth: + # type: mTLS + # mtls: + # secretName: secret-name + # secretNamespace: secret-namespace + + # -- staticEndpoint configuration. + # It is possible to specify static endpoint to scrape. If specified 'autodiscover' section is ignored. + # If set the static endpoint should be reachable, otherwise an error will be returned and the integration stops. + # Notice that if deployed as a daemonSet and not as a Deployment setting static URLs could lead to duplicate data + # staticEndpoint: + # url: https://url:port + # insecureSkipVerify: true + # auth: {} + + # -- Scheduler monitoring configuration + # @default -- Common settings for most K8s distributions. + scheduler: + # -- Enable scheduler monitoring. + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-scheduler" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + - selector: "k8s-app=kube-scheduler" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=openshift-kube-scheduler,scheduler=true" + namespace: openshift-kube-scheduler + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=openshift-kube-scheduler,scheduler=true" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10259 + insecureSkipVerify: true + auth: + type: bearer + # -- staticEndpoint configuration. + # It is possible to specify static endpoint to scrape. If specified 'autodiscover' section is ignored. + # If set the static endpoint should be reachable, otherwise an error will be returned and the integration stops. + # Notice that if deployed as a daemonSet and not as a Deployment setting static URLs could lead to duplicate data + # staticEndpoint: + # url: https://url:port + # insecureSkipVerify: true + # auth: {} + + # -- Controller manager monitoring configuration + # @default -- Common settings for most K8s distributions. + controllerManager: + # -- Enable controller manager monitoring. + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-controller-manager" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + - selector: "k8s-app=kube-controller-manager" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=kube-controller-manager,kube-controller-manager=true" + namespace: openshift-kube-controller-manager + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=kube-controller-manager,kube-controller-manager=true" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=controller-manager,controller-manager=true" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:10257 + insecureSkipVerify: true + auth: + type: bearer + # mtls: + # secretName: secret-name + # secretNamespace: secret-namespace + # -- staticEndpoint configuration. + # It is possible to specify static endpoint to scrape. If specified 'autodiscover' section is ignored. + # If set the static endpoint should be reachable, otherwise an error will be returned and the integration stops. + # Notice that if deployed as a daemonSet and not as a Deployment setting static URLs could lead to duplicate data + # staticEndpoint: + # url: https://url:port + # insecureSkipVerify: true + # auth: {} + + # -- API Server monitoring configuration + # @default -- Common settings for most K8s distributions. + apiServer: + # -- Enable API Server monitoring + enabled: true + autodiscover: + - selector: "tier=control-plane,component=kube-apiserver" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + # Endpoint distributions target: Kind(v1.22.1) + - url: https://localhost:6443 + insecureSkipVerify: true + auth: + type: bearer + - url: http://localhost:8080 + - selector: "k8s-app=kube-apiserver" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + - url: http://localhost:8080 + - selector: "app=openshift-kube-apiserver,apiserver=true" + namespace: openshift-kube-apiserver + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + - url: https://localhost:6443 + insecureSkipVerify: true + auth: + type: bearer + - selector: "app=openshift-kube-apiserver,apiserver=true" + namespace: kube-system + matchNode: true + endpoints: + - url: https://localhost:8443 + insecureSkipVerify: true + auth: + type: bearer + # -- staticEndpoint configuration. + # It is possible to specify static endpoint to scrape. If specified 'autodiscover' section is ignored. + # If set the static endpoint should be reachable, otherwise an error will be returned and the integration stops. + # Notice that if deployed as a daemonSet and not as a Deployment setting static URLs could lead to duplicate data + # staticEndpoint: + # url: https://url:port + # insecureSkipVerify: true + # auth: {} + +# -- Update strategy for the deployed DaemonSets. +# @default -- See `values.yaml` +updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + +# -- Update strategy for the deployed Deployments. +# @default -- `type: Recreate` +strategy: + type: Recreate + +# -- Adds extra attributes to the cluster and all the metrics emitted to the backend. Can be configured also with `global.customAttributes` +customAttributes: {} + +# -- Settings controlling ServiceAccount creation. +# @default -- See `values.yaml` +serviceAccount: + # -- (bool) Whether the chart should automatically create the ServiceAccount objects required to run. + # @default -- `true` + create: + annotations: {} + # If not set and create is true, a name is generated using the fullname template + name: "" + +# -- Additional labels for chart objects. Can be configured also with `global.labels` +labels: {} +# -- Annotations to be added to all pods created by the integration. +podAnnotations: {} +# -- Additional labels for chart pods. Can be configured also with `global.podLabels` +podLabels: {} + +# -- Run the integration with full access to the host filesystem and network. +# Running in this mode allows reporting fine-grained cpu, memory, process and network metrics for your nodes. +privileged: true +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- `false` +hostNetwork: +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: {} +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} + +# Settings controlling RBAC objects creation. +rbac: + # rbac.create -- Whether the chart should automatically create the RBAC objects required to run. + create: true + # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects. + pspEnabled: false + +# -- Sets pod/node affinities set almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +affinity: {} +# -- Sets pod's node selector almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +nodeSelector: {} +# -- Sets pod's tolerations to node taints almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +tolerations: [] + +# -- Config files for other New Relic integrations that should run in this cluster. +integrations: {} +# If you wish to monitor services running on Kubernetes you can provide integrations +# configuration under `integrations`. You just need to create a new entry where +# the key is the filename of the configuration file and the value is the content of +# the integration configuration. +# The data is the actual integration configuration as described in the spec here: +# https://docs.newrelic.com/docs/integrations/integrations-sdk/file-specifications/integration-configuration-file-specifications-agent-v180 +# For example, if you wanted to monitor a Redis instance that has a label "app=sampleapp" +# you could do so by adding following entry: +# nri-redis-sampleapp: +# discovery: +# command: +# # Run NRI Discovery for Kubernetes +# # https://github.com/newrelic/nri-discovery-kubernetes +# exec: /var/db/newrelic-infra/nri-discovery-kubernetes +# match: +# label.app: sampleapp +# integrations: +# - name: nri-redis +# env: +# # using the discovered IP as the hostname address +# HOSTNAME: ${discovery.ip} +# PORT: 6379 +# labels: +# env: test + +# -- (bool) Collect detailed metrics from processes running in the host. +# This defaults to true for accounts created before July 20, 2020. +# ref: https://docs.newrelic.com/docs/release-notes/infrastructure-release-notes/infrastructure-agent-release-notes/new-relic-infrastructure-agent-1120 +# @default -- `false` +enableProcessMetrics: + +# Prefix nodes display name with cluster to reduce chances of collisions +# prefixDisplayNameWithCluster: false + +# 'true' will use the node name as the name for the "host", +# note that it may cause data collision if the node name is the same in different clusters +# and prefixDisplayNameWithCluster is not set to true. +# 'false' will use the host name as the name for the "host". +# useNodeNameAsDisplayName: true + +selfMonitoring: + pixie: + # selfMonitoring.pixie.enabled -- Enables the Pixie Health Check nri-flex config. + # This Flex config performs periodic checks of the Pixie /healthz and /statusz endpoints exposed by the Pixie + # Cloud Connector. A status for each endpoint is sent to New Relic in a pixieHealthCheck event. + enabled: false + + +# -- Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` +proxy: "" + +# -- (bool) Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` +# @default -- `false` +nrStaging: +fedramp: + # -- (bool) Enables FedRAMP. Can be configured also with `global.fedramp.enabled` + # @default -- `false` + enabled: + +# -- (bool) Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` +# @default -- `false` +verboseLog: diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/.helmignore new file mode 100644 index 000000000..1ed4e226e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/.helmignore @@ -0,0 +1,25 @@ +# 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/ + +templates/apiservice/job-patch/README.md diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.lock new file mode 100644 index 000000000..23b2bd33c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T23:31:11.079152974Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.yaml new file mode 100644 index 000000000..76981dc13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +appVersion: 0.14.0 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic Kubernetes Metrics Adapter. +home: https://hub.docker.com/r/newrelic/newrelic-k8s-metrics-adapter +icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: newrelic-k8s-metrics-adapter +sources: +- https://github.com/newrelic/newrelic-k8s-metrics-adapter +- https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/main/charts/newrelic-k8s-metrics-adapter +version: 1.12.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md new file mode 100644 index 000000000..9c5428201 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md @@ -0,0 +1,139 @@ +[![New Relic Experimental header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Experimental.png)](https://opensource.newrelic.com/oss-category/#new-relic-experimental) + +# newrelic-k8s-metrics-adapter + +A Helm chart to deploy the New Relic Kubernetes Metrics Adapter. + +**Homepage:** + +## Source Code + +* +* + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| https://helm-charts.newrelic.com | common-library | 1.3.0 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Node affinity to use for scheduling. | +| apiServicePatchJob.image | object | See `values.yaml`. | Registry, repository, tag, and pull policy for the job container image. | +| apiServicePatchJob.volumeMounts | list | `[]` | Additional Volume mounts for Cert Job, you might want to mount tmp if Pod Security Policies. | +| apiServicePatchJob.volumes | list | `[]` | Additional Volumes for Cert Job. | +| certManager.enabled | bool | `false` | Use cert manager for APIService certs, rather than the built-in patch job. | +| config.accountID | string | `nil` | New Relic [Account ID](https://docs.newrelic.com/docs/accounts/accounts-billing/account-structure/account-id/) where the configured metrics are sourced from. (**Required**) | +| config.cacheTTLSeconds | int | `30` | Period of time in seconds in which a cached value of a metric is consider valid. | +| config.externalMetrics | string | See `values.yaml` | Contains all the external metrics definition of the adapter. Each key of the externalMetric entry represents the metric name and contains the parameters that defines it. | +| config.nrdbClientTimeoutSeconds | int | 30 | Defines the NRDB client timeout. The maximum allowed value is 120. | +| config.region | string | Automatically detected from `licenseKey`. | New Relic account region. If not set, it will be automatically derived from the License Key. | +| containerSecurityContext | string | `nil` | Configure containerSecurityContext | +| extraEnv | list | `[]` | Array to add extra environment variables | +| extraEnvFrom | list | `[]` | Array to add extra envFrom | +| extraVolumeMounts | list | `[]` | Add extra volume mounts | +| extraVolumes | list | `[]` | Array to add extra volumes | +| fullnameOverride | string | `""` | To fully override common.naming.fullname | +| image | object | See `values.yaml`. | Registry, repository, tag, and pull policy for the container image. | +| image.pullSecrets | list | `[]` | The image pull secrets. | +| nodeSelector | object | `{}` | Node label to use for scheduling. | +| personalAPIKey | string | `nil` | New Relic [Personal API Key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#user-api-key) (stored in a secret). Used to connect to NerdGraph in order to fetch the configured metrics. (**Required**) | +| podAnnotations | string | `nil` | Additional annotations to apply to the pod(s). | +| podSecurityContext | string | `nil` | Configure podSecurityContext | +| proxy | string | `nil` | Configure proxy for the metrics-adapter. | +| rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | +| replicas | int | `1` | Number of replicas in the deployment. | +| resources | object | See `values.yaml` | Resources you wish to assign to the pod. | +| serviceAccount.create | string | `true` | Specifies whether a ServiceAccount should be created for the job and the deployment. false avoids creation, true or empty will create the ServiceAccount | +| serviceAccount.name | string | Automatically generated. | If `serviceAccount.create` this will be the name of the ServiceAccount to use. If not set and create is true, a name is generated using the fullname template. If create is false, a serviceAccount with the given name must exist | +| tolerations | list | `[]` | List of node taints to tolerate (requires Kubernetes >= 1.6) | +| verboseLog | bool | `false` | Enable metrics adapter verbose logs. | + +## Example + +Make sure you have [added the New Relic chart repository.](../../README.md#install) + +Because of metrics configuration, we recommend to use an external values file to deploy the chart. An example with the required parameters looks like: + +```yaml +cluster: ClusterName +personalAPIKey: +config: + accountID: + externalMetrics: + nginx_average_requests: + query: "FROM Metric SELECT average(nginx.server.net.requestsPerSecond) SINCE 2 MINUTES AGO" +``` + +Then, to install this chart, run the following command: + +```sh +helm upgrade --install [release-name] newrelic-k8s-metrics-adapter/newrelic-k8s-metrics-adapter --values [values file path] +``` + +Once deployed the metric `nginx_average_requests` will be available to use by any HPA. This is and example of an HPA yaml using this metric: + +```yaml +kind: HorizontalPodAutoscaler +apiVersion: autoscaling/v2beta2 +metadata: + name: nginx-scaler +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: nginx + minReplicas: 1 + maxReplicas: 10 + metrics: + - type: External + external: + metric: + name: nginx_average_requests + selector: + matchLabels: + k8s.namespaceName: nginx + target: + type: Value + value: 10000 +``` + +The NRQL query that will be run to get the `nginx_average_requests` value will be: + +```sql +FROM Metric SELECT average(nginx.server.net.requestsPerSecond) WHERE clusterName='ClusterName' AND `k8s.namespaceName`='nginx' SINCE 2 MINUTES AGO +``` + +## External Metrics + +An example of multiple external metrics defined: + +```yaml +externalMetrics: + nginx_average_requests: + query: "FROM Metric SELECT average(nginx.server.net.requestsPerSecond) SINCE 2 MINUTES AGO" + container_average_cores_utilization: + query: "FROM Metric SELECT average(`k8s.container.cpuCoresUtilization`) SINCE 2 MINUTES AGO" +``` + +## Resources + +The default set of resources assigned to the newrelic-k8s-metrics-adapter pods is shown below: + +```yaml +resources: + limits: + memory: 80M + requests: + cpu: 100m + memory: 30M +``` + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl new file mode 100644 index 000000000..1de8c9553 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/README.md.gotmpl @@ -0,0 +1,107 @@ +[![New Relic Experimental header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Experimental.png)](https://opensource.newrelic.com/oss-category/#new-relic-experimental) + +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +## Example + +Make sure you have [added the New Relic chart repository.](../../README.md#install) + +Because of metrics configuration, we recommend to use an external values file to deploy the chart. An example with the required parameters looks like: + +```yaml +cluster: ClusterName +personalAPIKey: +config: + accountID: + externalMetrics: + nginx_average_requests: + query: "FROM Metric SELECT average(nginx.server.net.requestsPerSecond) SINCE 2 MINUTES AGO" +``` + +Then, to install this chart, run the following command: + +```sh +helm upgrade --install [release-name] newrelic-k8s-metrics-adapter/newrelic-k8s-metrics-adapter --values [values file path] +``` + +Once deployed the metric `nginx_average_requests` will be available to use by any HPA. This is and example of an HPA yaml using this metric: + +```yaml +kind: HorizontalPodAutoscaler +apiVersion: autoscaling/v2beta2 +metadata: + name: nginx-scaler +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: nginx + minReplicas: 1 + maxReplicas: 10 + metrics: + - type: External + external: + metric: + name: nginx_average_requests + selector: + matchLabels: + k8s.namespaceName: nginx + target: + type: Value + value: 10000 +``` + +The NRQL query that will be run to get the `nginx_average_requests` value will be: + +```sql +FROM Metric SELECT average(nginx.server.net.requestsPerSecond) WHERE clusterName='ClusterName' AND `k8s.namespaceName`='nginx' SINCE 2 MINUTES AGO +``` + +## External Metrics + +An example of multiple external metrics defined: + +```yaml +externalMetrics: + nginx_average_requests: + query: "FROM Metric SELECT average(nginx.server.net.requestsPerSecond) SINCE 2 MINUTES AGO" + container_average_cores_utilization: + query: "FROM Metric SELECT average(`k8s.container.cpuCoresUtilization`) SINCE 2 MINUTES AGO" +``` + +## Resources + +The default set of resources assigned to the newrelic-k8s-metrics-adapter pods is shown below: + +```yaml +resources: + limits: + memory: 80M + requests: + cpu: 100m + memory: 30M +``` + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml new file mode 100644 index 000000000..f0f9be1f9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/ci/test-values.yaml @@ -0,0 +1,14 @@ +global: + cluster: test-cluster + +personalAPIKey: "a21321" +verboseLog: false + +config: + accountID: 111 + region: EU + nrdbClientTimeoutSeconds: 30 + +image: + repository: e2e/newrelic-metrics-adapter + tag: "test" # Defaults to AppVersion diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl new file mode 100644 index 000000000..6a5f76503 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/_helpers.tpl @@ -0,0 +1,57 @@ +{{/* vim: set filetype=mustache: */}} + +{{- /* Allow to change pod defaults dynamically based if we are running in privileged mode or not */ -}} +{{- define "newrelic-k8s-metrics-adapter.securityContext.pod" -}} +{{- if include "newrelic.common.securityContext.pod" . -}} +{{- include "newrelic.common.securityContext.pod" . -}} +{{- else -}} +fsGroup: 1001 +runAsUser: 1001 +runAsGroup: 1001 +{{- end -}} +{{- end -}} + + + +{{/* +Select a value for the region +When this value is empty the New Relic client region will be the default 'US' +*/}} +{{- define "newrelic-k8s-metrics-adapter.region" -}} +{{- if .Values.config.region -}} + {{- .Values.config.region | upper -}} +{{- else if (include "newrelic.common.nrStaging" .) -}} +Staging +{{- else if hasPrefix "eu" (include "newrelic.common.license._licenseKey" .) -}} +EU +{{- end -}} +{{- end -}} + + + +{{- /* +Naming helpers +*/ -}} +{{- define "newrelic-k8s-metrics-adapter.name.apiservice" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "apiservice") }} +{{- end -}} + +{{- define "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" -}} +{{- if include "newrelic.common.serviceAccount.create" . -}} + {{- include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "apiservice") -}} +{{- else -}} + {{- include "newrelic.common.serviceAccount.name" . -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-k8s-metrics-adapter.name.apiservice-create" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "apiservice-create") }} +{{- end -}} + +{{- define "newrelic-k8s-metrics-adapter.name.apiservice-patch" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "apiservice-patch") }} +{{- end -}} + +{{- define "newrelic-k8s-metrics-adapter.name.hpa-controller" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "hpa-controller") }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml new file mode 100644 index 000000000..40bcba8b6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-clusterrolebinding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic.common.naming.fullname" . }}:system:auth-delegator + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml new file mode 100644 index 000000000..afb5d2d55 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/adapter-rolebinding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: kube-system + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: extension-apiserver-authentication-reader +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml new file mode 100644 index 000000000..8f01b6407 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/apiservice.yaml @@ -0,0 +1,19 @@ +apiVersion: apiregistration.k8s.io/v1 +kind: APIService +metadata: + name: v1beta1.external.metrics.k8s.io + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +{{- if .Values.certManager.enabled }} + annotations: + certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} + cert-manager.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} +{{- end }} +spec: + service: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + group: external.metrics.k8s.io + version: v1beta1 + groupPriorityMinimum: 100 + versionPriority: 100 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml new file mode 100644 index 000000000..5c364eb37 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrole.yaml @@ -0,0 +1,26 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - apiregistration.k8s.io + resources: + - apiservices + verbs: + - get + - update +{{- if .Values.rbac.pspEnabled }} + - apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml new file mode 100644 index 000000000..8aa95792e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/clusterrolebinding.yaml @@ -0,0 +1,19 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} +subjects: + - kind: ServiceAccount + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml new file mode 100644 index 000000000..6cf89b79e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-createSecret.yaml @@ -0,0 +1,55 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice-create" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice-create" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.image.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: create + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.apiServicePatchJob.image "context" .) }} + imagePullPolicy: {{ .Values.apiServicePatchJob.image.pullPolicy }} + args: + - create + - --host={{ include "newrelic.common.naming.fullname" . }},{{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + - --cert-name=tls.crt + - --key-name=tls.key + {{- with .Values.apiServicePatchJob.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.apiServicePatchJob.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml new file mode 100644 index 000000000..9d651c210 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/job-patchAPIService.yaml @@ -0,0 +1,53 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice-patch" . }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice-patch" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.image.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: patch + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.apiServicePatchJob.image "context" .) }} + imagePullPolicy: {{ .Values.apiServicePatchJob.image.pullPolicy }} + args: + - patch + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + - --apiservice-name=v1beta1.external.metrics.k8s.io + {{- with .Values.apiServicePatchJob.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.apiServicePatchJob.volumes }} + volumes: + {{- toYaml . | nindent 6 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml new file mode 100644 index 000000000..1dd6bc1a6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/psp.yaml @@ -0,0 +1,49 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled) (.Values.rbac.pspEnabled)) }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + privileged: false + # Required to prevent escalations to root. + # allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + # requiredDropCapabilities: + # - ALL + # Allow core volume types. + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + - 'persistentVolumeClaim' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Permits the container to run with root privileges as well. + rule: 'RunAsAny' + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml new file mode 100644 index 000000000..1e870e082 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/role.yaml @@ -0,0 +1,20 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml new file mode 100644 index 000000000..cbe8bdb72 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/rolebinding.yaml @@ -0,0 +1,20 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} +subjects: + - kind: ServiceAccount + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml new file mode 100644 index 000000000..68a3cfd73 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/apiservice/job-patch/serviceaccount.yaml @@ -0,0 +1,18 @@ +{{- $createServiceAccount := include "newrelic.common.serviceAccount.create" . -}} +{{- if (and $createServiceAccount (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic-k8s-metrics-adapter.name.apiservice.serviceAccount" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + # When hooks are sorted by weight and name, kind order gets overwritten, + # then this serviceAccount doesn't get created before dependent objects causing a failure. + # This weight is set, forcing it always to get created before the other objects. + # We submitted this PR to fix the issue: https://github.com/helm/helm/pull/10787 + "helm.sh/hook-weight": "-1" + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml new file mode 100644 index 000000000..8e88ad59e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/configmap.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + config.yaml: | + accountID: {{ .Values.config.accountID | required "config.accountID is required" }} + {{- with (include "newrelic-k8s-metrics-adapter.region" .) }} + region: {{ . }} + {{- end }} + cacheTTLSeconds: {{ .Values.config.cacheTTLSeconds | default "0" }} + {{- with .Values.config.externalMetrics }} + externalMetrics: + {{- toYaml . | nindent 6 }} + {{- end }} + nrdbClientTimeoutSeconds: {{ .Values.config.nrdbClientTimeoutSeconds | default "30" }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml new file mode 100644 index 000000000..1b96459a5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/deployment.yaml @@ -0,0 +1,113 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.podAnnotations }} + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + {{- with include "newrelic-k8s-metrics-adapter.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.image.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "newrelic.common.naming.name" . }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 10 }} + {{- end }} + args: + - --tls-cert-file=/tmp/k8s-metrics-adapter/serving-certs/tls.crt + - --tls-private-key-file=/tmp/k8s-metrics-adapter/serving-certs/tls.key + {{- if .Values.verboseLog }} + - --v=10 + {{- else }} + - --v=1 + {{- end }} + readinessProbe: + httpGet: + scheme: HTTPS + path: /healthz + port: 6443 + initialDelaySeconds: 1 + {{- if .Values.resources }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + {{- end }} + env: + - name: CLUSTER_NAME + value: {{ include "newrelic.common.cluster" . }} + - name: NEWRELIC_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.naming.fullname" . }} + key: personalAPIKey + {{- with (include "newrelic.common.proxy" .) }} + - name: HTTPS_PROXY + value: {{ . }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.extraEnvFrom }} + envFrom: {{ toYaml . | nindent 8 }} + {{- end }} + volumeMounts: + - name: tls-key-cert-pair + mountPath: /tmp/k8s-metrics-adapter/serving-certs/ + - name: config + mountPath: /etc/newrelic/adapter/ + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: tls-key-cert-pair + secret: + secretName: {{ include "newrelic-k8s-metrics-adapter.name.apiservice" . }} + - name: config + configMap: + name: {{ include "newrelic.common.naming.fullname" . }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml new file mode 100644 index 000000000..402fece01 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrole.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic.common.naming.fullname" . }}:external-metrics + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- apiGroups: + - external.metrics.k8s.io + resources: + - "*" + verbs: + - list + - get + - watch diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml new file mode 100644 index 000000000..390fab452 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/hpa-clusterrolebinding.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic-k8s-metrics-adapter.name.hpa-controller" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }}:external-metrics +subjects: +- kind: ServiceAccount + name: horizontal-pod-autoscaler + namespace: kube-system diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml new file mode 100644 index 000000000..09a70ab65 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +type: Opaque +stringData: + personalAPIKey: {{ .Values.personalAPIKey | required "personalAPIKey must be set" | quote }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/service.yaml new file mode 100644 index 000000000..82015830c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + ports: + - port: 443 + targetPort: 6443 + selector: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml new file mode 100644 index 000000000..b1e74523e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if include "newrelic.common.serviceAccount.annotations" . }} + annotations: + {{- include "newrelic.common.serviceAccount.annotations" . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml new file mode 100644 index 000000000..086160edc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/apiservice_test.yaml @@ -0,0 +1,22 @@ +suite: test naming helper for APIService's certmanager annotations and service name +templates: + - templates/apiservice/apiservice.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: Annotations are correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 11111111 + certManager: + enabled: true + asserts: + - matchRegex: + path: metadata.annotations["certmanager.k8s.io/inject-ca-from"] + pattern: ^my-namespace\/.*-root-cert + - matchRegex: + path: metadata.annotations["cert-manager.io/inject-ca-from"] + pattern: ^my-namespace\/.*-root-cert diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml new file mode 100644 index 000000000..82098ba1c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/common_extra_naming_test.yaml @@ -0,0 +1,27 @@ +suite: test naming helpers +templates: + - templates/adapter-clusterrolebinding.yaml + - templates/hpa-clusterrole.yaml + - templates/hpa-clusterrolebinding.yaml + - templates/apiservice/job-patch/clusterrole.yaml + - templates/apiservice/job-patch/clusterrolebinding.yaml + - templates/apiservice/job-patch/job-createSecret.yaml + - templates/apiservice/job-patch/job-patchAPIService.yaml + - templates/apiservice/job-patch/psp.yaml + - templates/apiservice/job-patch/rolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: default values has its name correctly defined + set: + cluster: test-cluster + personalAPIKey: 21321 + config: + accountID: 11111111 + rbac: + pspEnabled: true + asserts: + - matchRegex: + path: metadata.name + pattern: ^.*(-apiservice|-hpa-controller|:external-metrics|:system:auth-delegator) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml new file mode 100644 index 000000000..90b8798a7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/configmap_test.yaml @@ -0,0 +1,104 @@ +suite: test configmap region helper and externalMetrics +templates: + - templates/configmap.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: has the correct region when defined in local values + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + region: A-REGION + cacheTTLSeconds: 30 + nrdbClientTimeoutSeconds: 30 + - it: has the correct region when global staging + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + global: + nrStaging: true + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + region: Staging + cacheTTLSeconds: 30 + nrdbClientTimeoutSeconds: 30 + - it: has the correct region when global values and licenseKey is from eu + set: + personalAPIKey: 21321 + licenseKey: eu-whatever + cluster: test-cluster + config: + accountID: 111 + global: + aRandomGlobalValue: true + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + region: EU + cacheTTLSeconds: 30 + nrdbClientTimeoutSeconds: 30 + - it: has the correct region when no global values exist and licenseKey is from eu + set: + personalAPIKey: 21321 + cluster: test-cluster + licenseKey: eu-whatever + config: + accountID: 111 + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + region: EU + cacheTTLSeconds: 30 + nrdbClientTimeoutSeconds: 30 + - it: has no region when not defined and licenseKey is not from eu + set: + personalAPIKey: 21321 + cluster: test-cluster + licenseKey: us-whatever + config: + accountID: 111 + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + cacheTTLSeconds: 30 + nrdbClientTimeoutSeconds: 30 + - it: has externalMetrics when defined + set: + personalAPIKey: 21321 + cluster: test-cluster + licenseKey: us-whatever + config: + accountID: 111 + externalMetrics: + nginx_average_requests: + query: "FROM Metric SELECT average(nginx.server.net.requestsPerSecond)" + asserts: + - equal: + path: data["config.yaml"] + value: | + accountID: 111 + cacheTTLSeconds: 30 + externalMetrics: + nginx_average_requests: + query: FROM Metric SELECT average(nginx.server.net.requestsPerSecond) + nrdbClientTimeoutSeconds: 30 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml new file mode 100644 index 000000000..7a1898790 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/deployment_test.yaml @@ -0,0 +1,99 @@ +suite: test deployent images +release: + name: my-release + namespace: my-namespace +tests: + - it: has the correct image + set: + global: + cluster: test-cluster + personalAPIKey: 21321 + image: + repository: newrelic/newrelic-k8s-metrics-adapter + tag: "latest" + pullSecrets: + - name: regsecret + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: ^.*newrelic/newrelic-k8s-metrics-adapter:latest + template: templates/deployment.yaml + - equal: + path: spec.template.spec.imagePullSecrets + value: + - name: regsecret + template: templates/deployment.yaml + - it: correctly uses the cluster helper + set: + personalAPIKey: 21321 + config: + accountID: 111 + region: A-REGION + cluster: a-cluster + asserts: + - equal: + path: spec.template.spec.containers[0].env[0].value + value: a-cluster + template: templates/deployment.yaml + - it: correctly uses common.securityContext.podDefaults + set: + personalAPIKey: 21321 + config: + accountID: 111 + region: A-REGION + cluster: a-cluster + asserts: + - equal: + path: spec.template.spec.securityContext + value: + fsGroup: 1001 + runAsGroup: 1001 + runAsUser: 1001 + template: templates/deployment.yaml + - it: correctly uses common.proxy + set: + personalAPIKey: 21321 + config: + accountID: 111 + region: A-REGION + cluster: a-cluster + proxy: localhost:1234 + asserts: + - equal: + path: spec.template.spec.containers[0].env[2].value + value: localhost:1234 + template: templates/deployment.yaml + + - it: has a linux node selector by default + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + template: templates/deployment.yaml + + - it: has a linux node selector and additional selectors + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue + template: templates/deployment.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml new file mode 100644 index 000000000..4fba87fbe --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/hpa_clusterrolebinding_test.yaml @@ -0,0 +1,18 @@ +suite: test naming helper for clusterRolebBinding roleRef +templates: + - templates/hpa-clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: roleRef.name has its name correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: roleRef.name + pattern: ^.*:external-metrics diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml new file mode 100644 index 000000000..dd582313e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_cluster_rolebinding_test.yaml @@ -0,0 +1,22 @@ +suite: test job-patch RoleBinding and ClusterRoleBinding rendering and roleRef/Subjects names +templates: + - templates/apiservice/job-patch/rolebinding.yaml + - templates/apiservice/job-patch/clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: roleRef apiGroup and Subjets are correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: roleRef.name + pattern: ^.*-apiservice + - matchRegex: + path: subjects[0].name + pattern: ^.*-apiservice diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml new file mode 100644 index 000000000..33a1eaa73 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_clusterrole_test.yaml @@ -0,0 +1,20 @@ +suite: test job-patch clusterRole rule resourceName and rendering +templates: + - templates/apiservice/job-patch/clusterrole.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: PodSecurityPolicy rule resourceName is correctly defined + set: + rbac: + pspEnabled: true + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: rules[1].resourceNames[0] + pattern: ^.*-apiservice diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml new file mode 100644 index 000000000..91cd791d1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_common_test.yaml @@ -0,0 +1,27 @@ +suite: test labels and rendering for job-batch objects +templates: + - templates/apiservice/job-patch/clusterrole.yaml + - templates/apiservice/job-patch/clusterrolebinding.yaml + - templates/apiservice/job-patch/job-createSecret.yaml + - templates/apiservice/job-patch/job-patchAPIService.yaml + - templates/apiservice/job-patch/psp.yaml + - templates/apiservice/job-patch/role.yaml + - templates/apiservice/job-patch/rolebinding.yaml + - templates/apiservice/job-patch/serviceaccount.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: If customTLSCertificate and Certmanager enabled do not render + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + customTLSCertificate: a-tls-cert + certManager: + enabled: true + asserts: + - hasDocuments: + count: 0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml new file mode 100644 index 000000000..6db79234f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_createsecret_test.yaml @@ -0,0 +1,47 @@ +suite: test naming helper for job-createSecret +templates: + - templates/apiservice/job-patch/job-createSecret.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: spec metadata name is is correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - equal: + path: spec.template.metadata.name + value: my-release-newrelic-k8s-metrics-adapter-apiservice-create + - it: container args are correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: spec.template.spec.containers[0].args[1] + pattern: --host=.*,.*\.my-namespace.svc + - matchRegex: + path: spec.template.spec.containers[0].args[3] + pattern: --secret-name=.*-apiservice + - it: has the correct image + set: + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + personalAPIKey: 21321 + apiServicePatchJob: + image: + repository: registry.k8s.io/ingress-nginx/kube-webhook-certgen + tag: "latest" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: ^.*registry.k8s.io/ingress-nginx/kube-webhook-certgen:latest diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml new file mode 100644 index 000000000..0be083313 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_patch_job_patchapiservice_test.yaml @@ -0,0 +1,56 @@ +suite: test naming helper for job-patchAPIService +templates: + - templates/apiservice/job-patch/job-patchAPIService.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: spec metadata name is is correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: spec.template.metadata.name + pattern: .*-apiservice-patch$ + - it: container args are correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: spec.template.spec.containers[0].args[2] + pattern: ^--secret-name=.*-apiservice + + - it: serviceAccountName is correctly defined + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - matchRegex: + path: spec.template.spec.serviceAccountName + pattern: .*-apiservice$ + - it: has the correct image + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + apiServicePatchJob: + image: + repository: registry.k8s.io/ingress-nginx/kube-webhook-certgen + tag: "latest" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: .*registry.k8s.io/ingress-nginx/kube-webhook-certgen:latest$ diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml new file mode 100644 index 000000000..9b6207c35 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/job_serviceaccount_test.yaml @@ -0,0 +1,79 @@ +suite: test job' serviceAccount +templates: + - templates/apiservice/job-patch/job-createSecret.yaml + - templates/apiservice/job-patch/job-patchAPIService.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: my-release-newrelic-k8s-metrics-adapter-apiservice + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + + - it: has a linux node selector by default + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + + - it: has a linux node selector and additional selectors + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml new file mode 100644 index 000000000..78884c022 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/tests/rbac_test.yaml @@ -0,0 +1,50 @@ +suite: test RBAC creation +templates: + - templates/apiservice/job-patch/rolebinding.yaml + - templates/apiservice/job-patch/clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: subjects[0].name + value: my-release-newrelic-k8s-metrics-adapter-apiservice + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: subjects[0].name + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + personalAPIKey: 21321 + cluster: test-cluster + config: + accountID: 111 + region: A-REGION + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: subjects[0].name + value: default diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/values.yaml new file mode 100644 index 000000000..5c610f792 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-k8s-metrics-adapter/values.yaml @@ -0,0 +1,156 @@ +# IMPORTANT: The Kubernetes cluster name +# https://docs.newrelic.com/docs/kubernetes-monitoring-integration +# +# licenseKey: +# cluster: +# IMPORTANT: the previous values can also be set as global so that they +# can be shared by other newrelic product's charts. +# +# global: +# licenseKey: +# cluster: +# nrStaging: + +# -- New Relic [Personal API Key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#user-api-key) (stored in a secret). Used to connect to NerdGraph in order to fetch the configured metrics. (**Required**) +personalAPIKey: + +# -- Enable metrics adapter verbose logs. +verboseLog: false + +config: + # -- New Relic [Account ID](https://docs.newrelic.com/docs/accounts/accounts-billing/account-structure/account-id/) where the configured metrics are sourced from. (**Required**) + accountID: + + # config.region -- New Relic account region. If not set, it will be automatically derived from the License Key. + # @default -- Automatically detected from `licenseKey`. + region: + # For US-based accounts, the region is: `US`. + # For EU-based accounts, the region is: `EU`. + # For Staging accounts, the region is: 'Staging' this is also automatically derived form `global.nrStaging` + + + # config.cacheTTLSeconds -- Period of time in seconds in which a cached value of a metric is consider valid. + cacheTTLSeconds: 30 + # Not setting it or setting it to '0' disables the cache. + + # config.externalMetrics -- Contains all the external metrics definition of the adapter. Each key of the externalMetric entry represents the metric name and contains the parameters that defines it. + # @default -- See `values.yaml` + externalMetrics: + # Names cannot contain uppercase characters and + # "/" or "%" characters. + # my_external_metric_name_example: + # + # NRQL query that will executed to obtain the metric value. + # The query must return just one value so is recommended to use aggregator functions like average or latest. + # Default time span for aggregator func is 1h so is recommended to use the SINCE clause to reduce the time span. + # query: "FROM Metric SELECT average(`k8s.container.cpuCoresUtilization`) SINCE 2 MINUTES AGO" + # + # By default a cluster filter is added to the query to ensure no cross cluster metrics are taking into account. + # The added filter is equivalent to WHERE `clusterName`=. + # If metrics are not from the cluster use removeClusterFilter. Default value for this parameter is false. + # removeClusterFilter: false + + # config.nrdbClientTimeoutSeconds -- Defines the NRDB client timeout. The maximum allowed value is 120. + # @default -- 30 + nrdbClientTimeoutSeconds: 30 + +# image -- Registry, repository, tag, and pull policy for the container image. +# @default -- See `values.yaml`. +image: + registry: + repository: newrelic/newrelic-k8s-metrics-adapter + tag: "" + pullPolicy: IfNotPresent + # It is possible to specify docker registry credentials. + # See https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + # image.pullSecrets -- The image pull secrets. + pullSecrets: [] + # - name: regsecret + +# -- Number of replicas in the deployment. +replicas: 1 + +# -- Resources you wish to assign to the pod. +# @default -- See `values.yaml` +resources: + limits: + memory: 80M + requests: + cpu: 100m + memory: 30M + +serviceAccount: + # -- Specifies whether a ServiceAccount should be created for the job and the deployment. + # false avoids creation, true or empty will create the ServiceAccount + # @default -- `true` + create: + # -- If `serviceAccount.create` this will be the name of the ServiceAccount to use. + # If not set and create is true, a name is generated using the fullname template. + # If create is false, a serviceAccount with the given name must exist + # @default -- Automatically generated. + name: + +# -- Configure podSecurityContext +podSecurityContext: + +# -- Configure containerSecurityContext +containerSecurityContext: + +# -- Array to add extra environment variables +extraEnv: [] +# -- Array to add extra envFrom +extraEnvFrom: [] +# -- Array to add extra volumes +extraVolumes: [] +# -- Add extra volume mounts +extraVolumeMounts: [] + +# -- Additional annotations to apply to the pod(s). +podAnnotations: + +# Due to security restrictions, some users might require to use a https proxy to route traffic over the internet. +# In this specific case, when the metrics adapter sends a request to the New Relic backend. If this is the case +# for you, set this value to your http proxy endpoint. +# -- Configure proxy for the metrics-adapter. +proxy: + +# Pod scheduling priority +# Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +# priorityClassName: high-priority + +# fullnameOverride -- To fully override common.naming.fullname +fullnameOverride: "" +# -- Node affinity to use for scheduling. +affinity: {} +# -- Node label to use for scheduling. +nodeSelector: {} +# -- List of node taints to tolerate (requires Kubernetes >= 1.6) +tolerations: [] + +apiServicePatchJob: + # apiServicePatchJob.image -- Registry, repository, tag, and pull policy for the job container image. + # @default -- See `values.yaml`. + image: + registry: # defaults to registry.k8s.io + repository: ingress-nginx/kube-webhook-certgen + tag: v1.3.0 + pullPolicy: IfNotPresent + + # -- Additional Volumes for Cert Job. + volumes: [] + # - name: tmp + # emptyDir: {} + + # -- Additional Volume mounts for Cert Job, you might want to mount tmp if Pod Security Policies. + volumeMounts: [] + # - name: tmp + # mountPath: /tmp + # Enforce a read-only root. + +certManager: + # -- Use cert manager for APIService certs, rather than the built-in patch job. + enabled: false + +rbac: + # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects. + pspEnabled: false diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.lock new file mode 100644 index 000000000..064abf8aa --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.0 +digest: sha256:fa87cb007564a39a72739a3e850a91d6b03c0fc27a1115deac042b3ef77b4142 +generated: "2024-07-17T19:29:15.951407+05:30" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.yaml new file mode 100644 index 000000000..ce817fc00 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +appVersion: 2.0.2 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.2.0 +description: A Helm chart to deploy New Relic Kubernetes Logging as a DaemonSet, supporting + both Linux and Windows nodes and containers +home: https://github.com/newrelic/kubernetes-logging +icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg +keywords: +- logging +- newrelic +maintainers: +- email: logging-team@newrelic.com + name: jsubirat +- name: danybmx +- name: sdaubin +name: newrelic-logging +version: 1.23.1 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/README.md new file mode 100644 index 000000000..1635b0d86 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/README.md @@ -0,0 +1,268 @@ +# newrelic-logging + + +## Chart Details +New Relic offers a [Fluent Bit](https://fluentbit.io/) output [plugin](https://github.com/newrelic/newrelic-fluent-bit-output) to easily forward your logs to [New Relic Logs](https://docs.newrelic.com/docs/logs/new-relic-logs/get-started/introduction-new-relic-logs). This plugin is also provided in a standalone Docker image that can be installed in a [Kubernetes](https://kubernetes.io/) cluster in the form of a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/), which we refer as the Kubernetes plugin. + +This document explains how to install it in your cluster using our [Helm](https://helm.sh/) chart. + + +## Install / Upgrade / Uninstall instructions +Despite the `newrelic-logging` chart being able to work standalone, we recommend installing it as part of the [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) chart. The best way of doing so is through the guided installation process documented [here](https://docs.newrelic.com//docs/kubernetes-pixie/kubernetes-integration/installation/kubernetes-integration-install-configure/). This guided install can generate the Helm 3 commands required to install it (select "Helm 3" in Step 3 from the previous documentation link). You can also opt to install it manually using Helm by following [these steps](https://docs.newrelic.com//docs/kubernetes-pixie/kubernetes-integration/installation/install-kubernetes-integration-using-helm/#install-k8-helm). To uninstall it, refer to the steps outlined in [this page](https://docs.newrelic.com/docs/kubernetes-pixie/kubernetes-integration/uninstall-kubernetes/). + +### Installing or updating the helm New Relic repository + +To install the repo you can run: +``` +helm repo add newrelic https://helm-charts.newrelic.com +``` + +To update the repo you can run: +``` +helm repo update newrelic +``` + +## Configuration + +### How to configure the chart +The `newrelic-logging` chart can be installed either alone or as part of the [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) chart (recommended). The chart default settings should be suitable for most users. Nevertheless, you may be interested in overriding the defaults, either by passing them through a `values-newrelic.yaml` file or via the command line when installing the chart. Depending on how you installed it, you'll need to specify the `newrelic-logging`-specific configuration values using the chart name (`newrelic-logging`) as a prefix. In the table below, you can find a quick reference of how to configure the chart in these scenarios. The example depicts how you'd specify the mandatory `licenseKey` and `cluster` settings and how you'd override the `fluentBit.retryLimit` setting to `10`. + + + + + + + + + + + + + + + + + +
Installation methodConfiguration via values.yamlConfiguration via command line
Standalone newrelic-logging + + +``` +# values-newrelic.yaml configuration contents + +licenseKey: _YOUR_NEW_RELIC_LICENSE_KEY_ +cluster: _K8S_CLUSTER_NAME_ + +fluentBit: + retryLimit: 10 +``` + +``` +# Install / upgrade command + +helm upgrade --install newrelic-logging newrelic/newrelic-logging \ +--namespace newrelic \ +--create-namespace \ +-f values-newrelic.yaml +``` + + +``` +# Install / upgrade command + +helm upgrade --install newrelic-logging newrelic/newrelic-logging \ +--namespace=newrelic \ +--set licenseKey=_YOUR_NEW_RELIC_LICENSE_KEY_ \ +--set cluster=_K8S_CLUSTER_NAME_ \ +--set fluentBit.retryLimit=10 +``` +
As part of nri-bundle + +``` +# values-newrelic.yaml configuration contents + +# General settings that apply to all the child charts +global: + licenseKey: _YOUR_NEW_RELIC_LICENSE_KEY_ + cluster: _K8S_CLUSTER_NAME_ + +# Specific configuration for the newrelic-logging child chart +newrelic-logging: + fluentBit: + retryLimit: 10 +``` + +``` +# Install / upgrade command + +helm upgrade --install newrelic-bundle newrelic/nri-bundle \ + --namespace newrelic \ + --create-namespace \ + -f values-newrelic.yaml \ +``` + + +``` +# Install / upgrade command + +helm upgrade --install newrelic-bundle newrelic/nri-bundle \ +--namespace=newrelic \ +--set global.licenseKey=_YOUR_NEW_RELIC_LICENSE_KEY_ \ +--set global.cluster=_K8S_CLUSTER_NAME_ \ +--set newrelic-logging.fluentBit.retryLimit=10 +``` +
+ + +### Supported configuration parameters +See [values.yaml](values.yaml) for the default values + +| Parameter | Description | Default | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| `global.cluster` - `cluster` | The cluster name for the Kubernetes cluster. | | +| `global.licenseKey` - `licenseKey` | The [license key](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/license-key) for your New Relic Account. This will be the preferred configuration option if both `licenseKey` and `customSecret*` values are specified. | | +| `global.customSecretName` - `customSecretName` | Name of the Secret object where the license key is stored | | +| `global.customSecretLicenseKey` - `customSecretLicenseKey` | Key in the Secret object where the license key is stored. | | +| `global.fargate` | Must be set to `true` when deploying in an EKS Fargate environment. Prevents DaemonSet pods from being scheduled in Fargate nodes. | | +| `global.lowDataMode` - `lowDataMode` | If `true`, send minimal attributes on Kubernetes logs. Labels and annotations are not sent when lowDataMode is enabled. | `false` | +| `rbac.create` | Enable Role-based authentication | `true` | +| `rbac.pspEnabled` | Enable pod security policy support | `false` | +| `image.repository` | The container to pull. | `newrelic/newrelic-fluentbit-output` | +| `image.pullPolicy` | The pull policy. | `IfNotPresent` | +| `image.pullSecrets` | Image pull secrets. | `nil` | +| `image.tag` | The version of the container to pull. | See value in [values.yaml]` | +| `exposedPorts` | Any ports you wish to expose from the pod. Ex. 2020 for metrics | `[]` | +| `resources` | Any resources you wish to assign to the pod. | See Resources below | +| `priorityClassName` | Scheduling priority of the pod | `nil` | +| `nodeSelector` | Node label to use for scheduling on Linux nodes | `{ kubernetes.io/os: linux }` | +| `windowsNodeSelector` | Node label to use for scheduling on Windows nodes | `{ kubernetes.io/os: windows, node.kubernetes.io/windows-build: BUILD_NUMBER }` | +| `tolerations` | List of node taints to tolerate (requires Kubernetes >= 1.6) | See Tolerations below | +| `updateStrategy` | Strategy for DaemonSet updates (requires Kubernetes >= 1.6) | `RollingUpdate` | +| `extraVolumeMounts` | Additional DaemonSet volume mounts | `[]` | +| `extraVolumes` | Additional DaemonSet volumes | `[]` | +| `initContainers` | [Init containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) that will be executed before the actual container in charge of shipping logs to New Relic is initialized. Use this if you are using a custom Fluent Bit configuration that requires downloading certain files inside the volumes being accessed by the log-shipping pod. | `[]` | +| `windows.initContainers` | [Init containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) that will be executed before the actual container in charge of shipping logs to New Relic is initialized. Use this if you are using a custom Fluent Bit configuration that requires downloading certain files inside the volumes being accessed by the log-shipping pod. | `[]` | +| `serviceAccount.create` | If true, a service account would be created and assigned to the deployment | `true` | +| `serviceAccount.name` | The service account to assign to the deployment. If `serviceAccount.create` is true then this name will be used when creating the service account | | +| `serviceAccount.annotations` | The annotations to add to the service account if `serviceAccount.create` is set to true. | | +| `global.nrStaging` - `nrStaging` | Send data to staging (requires a staging license key) | `false` | +| `fluentBit.path` | Node path logs are forwarded from. Patterns are supported, as well as specifying multiple paths/patterns separated by commas. | `/var/log/containers/*.log` | +| `fluentBit.linuxMountPath` | The path mounted on linux Fluent-Bit pods to read logs from. Defaults to /var because some engines write the logs to /var/log and others to /var/lib (symlinked to /var/log) so Fluent-Bit need access to both in those cases | `/var` | +| `fluentBit.db` | Node path used by Fluent Bit to store a database file to keep track of monitored files and offsets. | `/var/log/containers/*.log` | +| `fluentBit.k8sBufferSize` | Set the buffer size for HTTP client when reading responses from Kubernetes API server. A value of 0 results in no limit and the buffer will expand as needed. | `32k` | +| `fluentBit.k8sLoggingExclude` | Set to "true" to allow excluding pods by adding the annotation `fluentbit.io/exclude: "true"` to pods you wish to exclude. | `false` | +| `fluentBit.additionalEnvVariables` | Additional environmental variables for fluentbit pods | `[]]` | +| `fluentBit.persistence.mode` | The [persistence mode](#Fluent-Bit-persistence-modes) you want to use, options are "hostPath", "none" or "persistentVolume" (this last one available only for linux) | +| `fluentBit.persistence.persistentVolume.storageClass` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), indicates the storage class that will be used for create the PersistentVolume and PersistentVolumeClaim. | | +| `fluentBit.persistence.persistentVolume.size` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), indicates the capacity for the PersistentVolume and PersistentVolumeClaim | 10Gi | +| `fluentBit.persistence.persistentVolume.dynamicProvisioning` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), indicates if the storage class used provide dynamic provisioning. If it does, only the PersistentVolumeClaim will be created. | true | +| `fluentBit.persistence.persistentVolume.existingVolume` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), indicates and existing volume in case you want to reuse one, bear in mind that it should allow ReadWriteMany access mode. A PersistentVolumeClaim will be created using it. | | +| `fluentBit.persistence.persistentVolume.existingVolumeClaim` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), indicates and existing volume claim that will be used on the daemonset. It should allow ReadWriteMany access mode. | | +| `fluentBit.persistence.persistentVolume.annotations.volume` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), allows to add annotations to the PersistentVolume (if created). | | +| `fluentBit.persistence.persistentVolume.annotations.claim` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), allows to add annotations to the PersistentVolumeClaim (if created). | | +| `fluentBit.persistence.persistentVolume.extra.volume` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), allows to add extra properties to the PersistentVolume (if created). | | +| `fluentBit.persistence.persistentVolume.extra.claim` | On "persistentVolume" [persistence mode](#Fluent-Bit-persistence-modes), allows to add extra properties to the PersistentVolumeClaim (if created). | | +| `daemonSet.annotations` | The annotations to add to the `DaemonSet`. | | +| `podAnnotations` | The annotations to add to the `DaemonSet` created `Pod`s. | | +| `hostNetwork` | Set the hostNetwork property for fluentbit pods. | | +| `enableLinux` | Enable log collection from Linux containers. This is the default behavior. In case you are only interested of collecting logs from Windows containers, set this to `false`. | `true` | +| `enableWindows` | Enable log collection from Windows containers. Please refer to the [Windows support](#windows-support) section for more details. | `false` | +| `fluentBit.config.service` | Contains fluent-bit.conf Service config | | +| `fluentBit.config.inputs` | Contains fluent-bit.conf Inputs config | | +| `fluentBit.config.extraInputs` | Contains extra fluent-bit.conf Inputs config | | +| `fluentBit.config.filters` | Contains fluent-bit.conf Filters config | | +| `fluentBit.config.extraFilters` | Contains extra fluent-bit.conf Filters config | | +| `fluentBit.config.lowDataModeFilters` | Contains fluent-bit.conf Filters config for lowDataMode | | +| `fluentBit.config.outputs` | Contains fluent-bit.conf Outputs config | | +| `fluentBit.config.extraOutputs` | Contains extra fluent-bit.conf Outputs config | | +| `fluentBit.config.parsers` | Contains parsers.conf Parsers config | | +| `fluentBit.retryLimit` | Amount of times to retry sending a given batch of logs to New Relic. This prevents data loss if there is a temporary network disruption, if a request to the Logs API is lost or when receiving a recoverable HTTP response. Set it to "False" for unlimited retries. | 5 | +| `fluentBit.sendMetrics` | Enable the collection of Fluent Bit internal metrics in Prometheus format as well as newrelic-fluent-bit-output internal plugin metrics. See [this documentation page](https://docs.newrelic.com/docs/logs/forward-logs/kubernetes-plugin-log-forwarding/#troubleshoot-installation) for more details. | `false` | +| `dnsConfig` | [DNS configuration](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) that will be added to the pods. Can be configured also with `global.dnsConfig`. | `{}` | +| `fluentBit.criEnabled` | We assume that `kubelet`directly communicates with the container engine using the [CRI](https://kubernetes.io/docs/concepts/overview/components/#container-runtime) specification. Set this to `false` if your K8s installation uses [dockershim](https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/) instead, in order to get the logs properly parsed. | `true` | + +### Fluent Bit persistence modes + +Fluent Bit uses a database file to keep track of log lines read from files (offsets). This database file is stored in the host node by default, using a `hostPath` mount. It's specifically stored (by default) in `/var/log/flb_kube.db` to keep things simple, as we're already mounting `/var` for accessing container logs. + +Sometimes the security constraints of some clusters don't allow mounting `hostPath`s in read-write mode. That's why you can chose among the following +persistence modes. Each one has their pros and cons. + +- `hostPath` (default) will use a `hostPath` mount to store the DB file on the node disk. This is the easiest, cheapest an most reliable option, but prohibited by some cloud vendor security policies. +- `none` will disable the Fluent Bit DB file. This can cause log duplication or data loss in case Fluent Bit gets restarted. +- `persistentVolume` (Linux only) will use a `ReadWriteMany` persistent volume to store the DB file. This will override the `fluentBit.db` path and use `/db/${NODE_NAME}-fb.db` instead. If you use this option in a Windows cluster it will default to `none` on Windows nodes. + +#### GKE Autopilot example + +If you're using the `persistentVolume` persistence mode you need to provide at least the `storageClass`, and it should be `ReadWriteMany`. This is an example of the configuration for persistence in [GKE Autopilot](https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview). + +``` +fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: standard-rwx + linuxMountPath: /var/log +``` + +### Proxy support + +Since Fluent Bit Kubernetes plugin is using [newrelic-fluent-bit-output](https://github.com/newrelic/newrelic-fluent-bit-output) we can configure the [proxy support](https://github.com/newrelic/newrelic-fluent-bit-output#proxy-support) in order to set up the proxy configuration. + +#### As environment variables + +The easiest way to configure the proxy is by means of specifying the `HTTP_PROXY` or `HTTPS_PROXY` variables as follows: + +``` +# values-newrelic.yml + +fluentBit: + additionalEnvVariables: + - name: HTTPS_PROXY + value: https://your-https-proxy-hostname:3129 +``` + + +#### Custom proxy configuration (for proxies using self-signed certificates) + +If you need to use a proxy using self-signed certificates, you'll need to mount a volume with the Certificate Authority +bundle file and reference it from the Fluent Bit configuration as follows: + +``` +# values-newrelic.yaml +extraVolumes: [] + - name: proxyConfig + # Example using hostPath. You can also place the caBundleFile.pem contents in a ConfigMap and reference it here instead, + # as explained here: https://kubernetes.io/docs/concepts/storage/volumes/#configmap + hostPath: + path: /path/in/node/to/your/caBundleFile.pem + +extraVolumeMounts: [] + - name: proxyConfig + mountPath: /proxyConfig/caBundleFile.pem + +fluentBit: + config: + outputs: | + [OUTPUT] + Name newrelic + Match * + licenseKey ${LICENSE_KEY} + endpoint ${ENDPOINT} + lowDataMode ${LOW_DATA_MODE} + Retry_Limit ${RETRY_LIMIT} + proxy https://your-https-proxy-hostname:3129 + caBundleFile /proxyConfig/caBundleFile.pem +``` + + +## Windows support + +Since version `1.7.0`, this Helm chart supports shipping logs from Windows containers. To this end, you need to set the `enableWindows` configuration parameter to `true`. + +Windows containers have some constraints regarding Linux containers. The main one being that they can only be executed on _hosts_ using the exact same Windows version and build number. On the other hand, Kubernetes nodes only supports the Windows versions listed [here](https://kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#windows-os-version-support). + +This Helm chart deploys one `DaemonSet` for each of the Windows versions it supports, while ensuring that only containers matching the host operating system will be deployed in each host. + +This Helm chart currently supports the following Windows versions: +- Windows Server LTSC 2019, build 10.0.17763 +- Windows Server LTSC 2022, build 10.0.20348 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/Chart.yaml new file mode 100644 index 000000000..b65ac15d4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.2.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..3ccc108e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/DEVELOPERS.md @@ -0,0 +1,663 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..647b4ff43 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-enable-windows-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-enable-windows-values.yaml new file mode 100644 index 000000000..870bc082a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-enable-windows-values.yaml @@ -0,0 +1,2 @@ +enableLinux: false +enableWindows: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-lowdatamode-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-lowdatamode-values.yaml new file mode 100644 index 000000000..7740338b0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-lowdatamode-values.yaml @@ -0,0 +1 @@ +lowDataMode: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml new file mode 100644 index 000000000..22dd7e05e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-override-global-lowdatamode.yaml @@ -0,0 +1,3 @@ +global: + lowDataMode: true +lowDataMode: false diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-staging-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-staging-values.yaml new file mode 100644 index 000000000..efbdccaf8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-staging-values.yaml @@ -0,0 +1 @@ +nrStaging: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-global.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-global.yaml new file mode 100644 index 000000000..490a0b7ed --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-global.yaml @@ -0,0 +1 @@ +global: {} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/ci/test-with-empty-values.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json new file mode 100644 index 000000000..cafdaf85c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/fluent-bit-and-plugin-metrics-dashboard-template.json @@ -0,0 +1,2237 @@ +{ + "name": "Kubernetes Fluent Bit monitoring", + "description": null, + "permissions": "PUBLIC_READ_WRITE", + "pages": [ + { + "name": "Fluent Bit metrics: General", + "description": null, + "widgets": [ + { + "title": "", + "layout": { + "column": 1, + "row": 1, + "width": 6, + "height": 6 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# README\n\n## About this page\nThis page represents most of [Fluent Bit's internal metrics](https://docs.fluentbit.io/manual/administration/monitoring#for-v2-metrics). The metric representations are grouped by categories and faceted by each plugin instance where appropriate.\n\n## How to filter\n1. Select the Kubernetes cluster you want to troubleshoot in the \"Cluster Name\" variable above.\n2. [OPTIONAL] You can use any of the values in the `Node name` and `Pod name` columns on the \"Fluent Bit version\" table to further filter the metrics displayed in the graphs below. To do so, you need to enable [facet filtering](https://docs.newrelic.com/docs/query-your-data/explore-query-data/dashboards/filter-new-relic-one-dashboards-facets/) on that table by clicking on the \"Edit\" submenu and select \"Filter the current dashboard\" under \"Facet Linking\". \n\n## Legend\n### Metric dimensions\n- **name**: the name of the Fluent Bit plugin. Version 1.21.0 of our Helm chart names them according to the plugin names described in the following section.\n- **pod_name**: the `newrelic-logging` pod (Fluent Bit instance) that emitted this metric.\n- **node_name**: physical Kubernetes node where the `newrelic-logging` pod is running.\n\n### Plugin names\n- **pod-logs-tailer**: `tail` *INPUT* plugin normally reading from `/var/log/containers/*.log`\n- **kubernetes-enricher**: `kubernetes` *FILTER* plugin that queries the Kubernetes API to enrich the logs with pod/container metadata.\n- **node-attributes-enricher**: `record_modifier` *FILTER* plugin that enriches logs with `cluster_name`.\n- **kubernetes-attribute-lifter** (only when in low data mode): `nest` *FILTER* plugin that lifts all the keys under `kubernetes`. This plugin is transparent to the final shape of the log.\n- **node-attributes-enricher-filter** (only when in low data mode): same as node-attributes-enricher`, but it also removes attributes that are not strictly necessary for correct platform functioning.\n- **newrelic-logs-forwarder**: `newrelic` *OUTPUT* plugin that sends logs to the New Relic Logs API" + } + }, + { + "title": "Fluent Bit version", + "layout": { + "column": 7, + "row": 1, + "width": 6, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.table" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT latest(os) as 'OS', latest(version) as 'FB version', latest(cluster_name) FROM Metric where metricName = 'fluentbit_build_info' AND cluster_name IN ({{cluster_name}}) since 1 hour ago facet pod_name, node_name limit max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + } + } + }, + { + "title": "Fluent Bit uptime", + "layout": { + "column": 7, + "row": 4, + "width": 6, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT latest(fluentbit_uptime) FROM Metric where cluster_name IN ({{cluster_name}}) facet pod_name timeseries" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 7, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# INPUTS" + } + }, + { + "title": "Input byte rate (bytes/minute)", + "layout": { + "column": 1, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_input_bytes_total), 1 minute) as 'bytes/minute' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) timeseries max facet name, pod_name" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Input log rate (records/minute)", + "layout": { + "column": 5, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_input_records_total), 1 minute) as 'logs/minute' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average incoming record size (bytes)", + "layout": { + "column": 9, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_input_bytes_total)/sum(fluentbit_input_records_total) as 'Average incoming record size (bytes)' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 11, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# FILTERS" + } + }, + { + "title": "Filter byte rate (bytes/minute)", + "layout": { + "column": 1, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_bytes_total), 1 minute) FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Filter log rate (records/minute)", + "layout": { + "column": 5, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_records_total), 1 minute) FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average filtered record size (bytes)", + "layout": { + "column": 9, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_filter_bytes_total)/sum(fluentbit_filter_records_total) AS 'Average filtered record size (bytes)' FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Record add/drop rate per FILTER plugin", + "layout": { + "column": 1, + "row": 15, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_add_records_total), 1 minute) as 'Added back to pipeline', rate(sum(fluentbit_filter_drop_records_total), 1 minute) as 'Removed from pipeline' FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries MAX" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "REQUESTS_PER_MINUTE" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 18, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# OUTPUTS" + } + }, + { + "title": "Output byte rate (bytes/minute)", + "layout": { + "column": 1, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_bytes_total), 1 minute) as 'bytes/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Output log rate (records/minute)", + "layout": { + "column": 5, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_records_total), 1 minute) as 'records/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average outgoing record size (bytes)", + "layout": { + "column": 9, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_output_proc_bytes_total)/sum(fluentbit_output_proc_records_total) as 'bytes' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries MAX" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "newrelic plugin statistics (records/minute)", + "layout": { + "column": 1, + "row": 22, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_records_total), 1 minute) as 'Processed', rate(sum(fluentbit_output_dropped_records_total), 1 minute) as 'Dropped', rate(sum(fluentbit_output_retried_records_total), 1 minute) as 'Retried' FROM Metric where cluster_name IN ({{cluster_name}}) AND name = 'newrelic-logs-forwarder' facet pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Other OUTPUT plugin statistics (records/minute)", + "layout": { + "column": 5, + "row": 22, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_records_total), 1 minute) as 'Processed', rate(sum(fluentbit_output_dropped_records_total), 1 minute) as 'Dropped', rate(sum(fluentbit_output_retried_records_total), 1 minute) as 'Retried' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'newrelic-logs-forwarder' and name != 'fb-metrics-forwarder' facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Connections per OUTPUT plugin", + "layout": { + "column": 9, + "row": 22, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_output_upstream_total_connections) as 'Total', max(fluentbit_output_upstream_busy_connections) as 'Busy' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries MAX" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "newrelic plugin errors (errors/minute)", + "layout": { + "column": 1, + "row": 25, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_errors_total), 1 minute) AS 'Errors/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name = 'newrelic-logs-forwarder' facet pod_name timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "newrelic plugin chunk retry statistics (retries/minute)", + "layout": { + "column": 5, + "row": 25, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_retries_total), 1 minute) as 'Retries', rate(sum(fluentbit_output_retries_failed_total), 1 minute) as 'Expirations' FROM Metric where cluster_name IN ({{cluster_name}}) AND name = 'newrelic-logs-forwarder' facet pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 28, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# MEMORY USAGE" + } + }, + { + "title": "Input plugin memory usage", + "layout": { + "column": 1, + "row": 29, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_memory_bytes) as 'Max' FROM Metric where cluster_name IN ({{cluster_name}}) and name != 'fb-metrics-collector' timeseries max facet name, pod_name " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "INPUT memory buffer over limit", + "layout": { + "column": 5, + "row": 29, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "colors": { + "seriesOverrides": [ + { + "color": "#013ef4", + "seriesName": "pod-logs-tailer" + } + ] + }, + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_overlimit) FROM Metric where cluster_name IN ({{cluster_name}}) and name != 'fb-metrics-collector' timeseries max facet name, pod_name" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true, + "thresholds": [ + { + "from": 0.95, + "name": "Mem buf overlimit", + "severity": "critical", + "to": 1.05 + } + ] + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Chunk statistics per INPUT plugin", + "layout": { + "column": 9, + "row": 29, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT average(fluentbit_input_storage_chunks_up) AS 'Up (in memory)', average(fluentbit_input_storage_chunks_down) AS 'Down (in fs)', average(fluentbit_input_storage_chunks_busy) AS 'Busy', average(fluentbit_input_storage_chunks) as 'Total' FROM Metric where name != 'fb-metrics-collector' since 1 hour ago timeseries MAX facet name, pod_name " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Buffered chunks", + "layout": { + "column": 1, + "row": 32, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_chunks) AS 'Total', max(fluentbit_storage_mem_chunks) AS 'Memory', max(fluentbit_storage_fs_chunks) AS 'Filesystem' FROM Metric where cluster_name IN ({{cluster_name}}) facet pod_name timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Busy chunks' size", + "layout": { + "column": 5, + "row": 32, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_chunks_busy_bytes) FROM Metric where name != 'fb-metrics-collector' facet name, pod_name timeseries MAX since 1 hour ago" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Filesystem chunks state", + "layout": { + "column": 9, + "row": 32, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT average(fluentbit_storage_fs_chunks_up) AS 'Up (in memory)', average(fluentbit_storage_fs_chunks_down) AS 'Down (fs only)' FROM Metric since '2024-02-29 13:22:00+0000' UNTIL '2024-02-29 14:31:00+0000' timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + } + ] + }, + { + "name": "Fluent Bit metrics: Pipeline View", + "description": null, + "widgets": [ + { + "title": "", + "layout": { + "column": 1, + "row": 1, + "width": 6, + "height": 6 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# README\n\n## About this page\nThis page represents the same metrics that are displayed in the \"Fluent Bit metrics: General\" page. Nevertheless, they are grouped differently to allow you to visualize a given metric across the whole pipeline with a single glance.\n\n## How to filter\n1. Select the Kubernetes cluster you want to troubleshoot in the \"Cluster Name\" variable above.\n2. [OPTIONAL] You can use any of the values in the `Node name` and `Pod name` columns on the \"Fluent Bit version\" table to further filter the metrics displayed in the graphs below. To do so, you need to enable [facet filtering](https://docs.newrelic.com/docs/query-your-data/explore-query-data/dashboards/filter-new-relic-one-dashboards-facets/) on that table by clicking on the \"Edit\" submenu and select \"Filter the current dashboard\" under \"Facet Linking\". \n\n## Legend\n### Metric dimensions\n- **name**: the name of the Fluent Bit plugin. Version 1.21.0 of our Helm chart names them according to the plugin names described in the following section.\n- **pod_name**: the `newrelic-logging` pod (Fluent Bit instance) that emitted this metric.\n- **node_name**: physical Kubernetes node where the `newrelic-logging` pod is running.\n\n### Plugin names\n- **pod-logs-tailer**: `tail` *INPUT* plugin normally reading from `/var/log/containers/*.log`\n- **kubernetes-enricher**: `kubernetes` *FILTER* plugin that queries the Kubernetes API to enrich the logs with pod/container metadata.\n- **node-attributes-enricher**: `record_modifier` *FILTER* plugin that enriches logs with `cluster_name`.\n- **kubernetes-attribute-lifter** (only when in low data mode): `nest` *FILTER* plugin that lifts all the keys under `kubernetes`. This plugin is transparent to the final shape of the log.\n- **node-attributes-enricher-filter** (only when in low data mode): same as node-attributes-enricher`, but it also removes attributes that are not strictly necessary for correct platform functioning.\n- **newrelic-logs-forwarder**: `newrelic` *OUTPUT* plugin that sends logs to the New Relic Logs API" + } + }, + { + "title": "Fluent Bit version", + "layout": { + "column": 7, + "row": 1, + "width": 6, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.table" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT latest(os) as 'OS', latest(version) as 'FB version', latest(cluster_name) FROM Metric where metricName = 'fluentbit_build_info' AND cluster_name IN ({{cluster_name}}) since 1 hour ago facet pod_name, node_name limit max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + } + } + }, + { + "title": "Fluent Bit uptime", + "layout": { + "column": 7, + "row": 4, + "width": 6, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT latest(fluentbit_uptime) FROM Metric where cluster_name IN ({{cluster_name}}) timeseries facet pod_name " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "SECONDS" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 7, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# BYTE RATES" + } + }, + { + "title": "Input byte rate (bytes/minute)", + "layout": { + "column": 1, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_input_bytes_total), 1 minute) as 'bytes/minute' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) timeseries max facet name, pod_name" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Filter byte rate (bytes/minute)", + "layout": { + "column": 5, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_bytes_total), 1 minute) FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Output byte rate (bytes/minute)", + "layout": { + "column": 9, + "row": 8, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_bytes_total), 1 minute) as 'bytes/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 11, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# LOG RECORD RATES" + } + }, + { + "title": "Input log rate (records/minute)", + "layout": { + "column": 1, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_input_records_total), 1 minute) as 'logs/minute' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Filter log rate (records/minute)", + "layout": { + "column": 5, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_records_total), 1 minute) FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Output log rate (records/minute)", + "layout": { + "column": 9, + "row": 12, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_proc_records_total), 1 minute) as 'records/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Record add/drop rate per FILTER plugin", + "layout": { + "column": 5, + "row": 15, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_filter_add_records_total), 1 minute) as 'Added back to pipeline', rate(sum(fluentbit_filter_drop_records_total), 1 minute) as 'Removed from pipeline' FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries MAX" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "REQUESTS_PER_MINUTE" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 18, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# AVERAGE LOG RECORD SIZES AT THE END OF EACH STAGE" + } + }, + { + "title": "Average incoming record size (bytes)", + "layout": { + "column": 1, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_input_bytes_total)/sum(fluentbit_input_records_total) as 'Average incoming record size (bytes)' FROM Metric where name != 'fb-metrics-collector' and cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average filtered record size (bytes)", + "layout": { + "column": 5, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_filter_bytes_total)/sum(fluentbit_filter_records_total) AS 'Average filtered record size (bytes)' FROM Metric WHERE cluster_name IN ({{cluster_name}}) facet name, pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average outgoing record size (bytes)", + "layout": { + "column": 9, + "row": 19, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT sum(fluentbit_output_proc_bytes_total)/sum(fluentbit_output_proc_records_total) as 'bytes' FROM Metric where cluster_name IN ({{cluster_name}}) AND name != 'fb-metrics-forwarder' facet name, pod_name timeseries MAX" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "", + "layout": { + "column": 1, + "row": 22, + "width": 12, + "height": 1 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# MEMORY USAGE AND BACKPRESSURE" + } + }, + { + "title": "Input plugin memory usage", + "layout": { + "column": 1, + "row": 23, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_memory_bytes) as 'Max' FROM Metric where cluster_name IN ({{cluster_name}}) and name != 'fb-metrics-collector' timeseries max facet name, pod_name " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Busy chunks' size", + "layout": { + "column": 5, + "row": 23, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_chunks_busy_bytes) FROM Metric where name != 'fb-metrics-collector' facet name, pod_name timeseries MAX since 1 hour ago" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "newrelic plugin chunk retry statistics (retries/minute)", + "layout": { + "column": 9, + "row": 23, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_retries_total), 1 minute) as 'Retries', rate(sum(fluentbit_output_retries_failed_total), 1 minute) as 'Expirations' FROM Metric where cluster_name IN ({{cluster_name}}) AND name = 'newrelic-logs-forwarder' facet pod_name timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "INPUT memory buffer over limit", + "layout": { + "column": 1, + "row": 26, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "colors": { + "seriesOverrides": [ + { + "color": "#013ef4", + "seriesName": "pod-logs-tailer" + } + ] + }, + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT max(fluentbit_input_storage_overlimit) FROM Metric where cluster_name IN ({{cluster_name}}) and name != 'fb-metrics-collector' timeseries max facet name, pod_name" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true, + "thresholds": [ + { + "from": 0.95, + "name": "Mem buf overlimit", + "severity": "critical", + "to": 1.05 + } + ] + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Chunk statistics per INPUT plugin", + "layout": { + "column": 5, + "row": 26, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT average(fluentbit_input_storage_chunks_up) AS 'Up (in memory)', average(fluentbit_input_storage_chunks_down) AS 'Down (in fs)', average(fluentbit_input_storage_chunks_busy) AS 'Busy', average(fluentbit_input_storage_chunks) as 'Total' FROM Metric where name != 'fb-metrics-collector' since 1 hour ago timeseries MAX facet name, pod_name " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "newrelic plugin errors (errors/minute)", + "layout": { + "column": 9, + "row": 26, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(fluentbit_output_errors_total), 1 minute) AS 'Errors/minute' FROM Metric where cluster_name IN ({{cluster_name}}) AND name = 'newrelic-logs-forwarder' facet pod_name timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": { + "isLabelVisible": true + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + } + ] + }, + { + "name": "newrelic-fluent-bit-output plugin metrics", + "description": null, + "widgets": [ + { + "title": "", + "layout": { + "column": 1, + "row": 1, + "width": 4, + "height": 9 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.markdown" + }, + "rawConfiguration": { + "text": "# README\n## About this page\nThis page displays metrics collected internally in the [New Relic Fluent Bit output plugin](https://github.com/newrelic/newrelic-fluent-bit-output) (in short, **NR FB plugin**). These metrics are independent of Fluent Bit's, and **must not be considered as a stable API: they can change its naming or dimensions at any time in newer plugin versions**.\n\nPlease note that **the NR FB plugin does not include the `pod_name` nor the `node_name` dimensions**. Therefore, the graphs below represent an aggregation of all your running Fluent Bit instances across one or more clusters. You can use the `cluster_name` dimension (or dashboard variable above) to narrow down the troubleshooting to one or more clusters.\n\n## Basic naming conventions\n- Fluent Bit aggregates logs in batches, also referred as **[chunks](https://docs.fluentbit.io/manual/administration/buffering-and-storage#chunks-memory-filesystem-and-backpressure)**. Each chunk therefore contains an unknown amount of logs.\n- Chunks are received sequentially at the NR FB plugin, which takes care of reading the logs they contain and splitting them into the so-called New Relic *payloads*.\n- Each **payload** is a compressed stream of bytes that can be [at most 1MB long](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/#limits), and follows the [data format required by the Logs API](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/#json-content).\n\n\n## Error-detection graphs and recommended actions\n\nThe following are the main graphs used to detect potential problems in your log forwarding setup. Refer to each section to learn the recommended actions for each graph.\n\n### Payload packaging errors\nRepresents the percentage of Fluent Bit chunks that threw an error when they were attempted to be packaged as New Relic payloads. Such errors are never expected to happen. Therefore, **any value greater than 0% should be thoroughly investigated**.\n\nIf you find errors in this graph, please open a support ticket and include a sample of your logs for further investigation.\n\n### Payload sending errors\nRepresents the percentage of New Relic payloads that threw an unexpected error when they were attempted to be sent to New Relic. Such errors can happen sporadically: timeouts due to poor network performance or sudden network changes can cause them from time to time. Observing **values greater than 0% can sometimes be normal, but any value above 10% should be considered as an annomalous situation and should be thoroughly investigated**.\n\nIf you find errors in this graph, please ensure that you don't have any weak spots in your network path to New Relic: are you using a proxy? Is it or any network hop introducing too much latency due to being saturated? If you can't find anything on you side, please open a support ticket and include as much information as possible from your network setup.\n\n### Payload send results\nRepresents the amount of API requests that were performed to send logs to New Relic. **Ideally, you should only observe 202 responses here**. Sometimes, intermediary CDN providers can introduce some errors (503 error codes) from time to time, in which case your logs will not be lost and reattempted to be sent.\n\nIf you find a considerable amount of non-202 responses in this graph, please open a customer support ticket.\n\n## Additional troubleshooting graphs\n\nThe following graphs include additional fine-grained information that will be useful for New Relic to troubleshoot your potential installation issues.\n\n### Average timings\nRepresents the average amount of time the plugin spent packaging the log payloads and sending them to New Relic, respectively.\n\n### Accumulated time per minute\nRepresents the amount of time per minute the plugin spent packaging the log payloads and sending them to New Relic, respectively.\n\n### Payload size\nRepresents the size in bytes of the individual compressed payloads sent to New Relic.\n\n### Payload packets per Fluent Bit chunk\nRepresents the amount of payloads sent to New Relic per each Fluent Bit chunk." + } + }, + { + "title": "Payload packaging errors", + "layout": { + "column": 5, + "row": 1, + "width": 2, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.billboard" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "FROM Metric SELECT percentage(count(`logs.fb.packaging.time`), WHERE hasError = true) AS 'packaging errors'" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": [ + { + "alertSeverity": "CRITICAL", + "value": 0 + } + ] + } + }, + { + "title": "Payload sending errors", + "layout": { + "column": 7, + "row": 1, + "width": 2, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.billboard" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "FROM Metric SELECT percentage(count(`logs.fb.payload.send.time`), WHERE hasError = true) AS 'send errors'" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "thresholds": [ + { + "alertSeverity": "WARNING", + "value": 0 + }, + { + "alertSeverity": "CRITICAL", + "value": 0.1 + } + ] + } + }, + { + "title": "Payload send results", + "layout": { + "column": 9, + "row": 1, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(count(logs.fb.payload.send.time), 1 minute) AS 'Status Code' FROM Metric FACET CASES(WHERE statusCode = 0 AS 'Send error') OR statusCode timeseries max" + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "units": { + "unit": "REQUESTS_PER_MINUTE" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Average timings", + "layout": { + "column": 5, + "row": 4, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT average(logs.fb.payload.send.time) AS 'Payload sending', average(logs.fb.packaging.time) AS 'Payload packaging' FROM Metric timeseries max" + } + ], + "nullValues": { + "nullValue": "zero" + }, + "platformOptions": { + "ignoreTimeRange": false + }, + "units": { + "unit": "MS" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Accumulated time per minute", + "layout": { + "column": 9, + "row": 4, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.area" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT rate(sum(logs.fb.total.send.time), 1 minute) AS 'Sending', rate(sum(logs.fb.packaging.time), 1 minute) AS 'Packaging' FROM Metric TIMESERIES max" + } + ], + "nullValues": { + "nullValue": "zero" + }, + "platformOptions": { + "ignoreTimeRange": false + }, + "units": { + "unit": "MS" + } + } + }, + { + "title": "Payload size", + "layout": { + "column": 5, + "row": 7, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT min(logs.fb.payload.size) AS 'Minimum', average(logs.fb.payload.size) AS 'Average', max(logs.fb.payload.size) AS 'Maximum' FROM Metric timeseries MAX " + } + ], + "nullValues": { + "nullValue": "default" + }, + "platformOptions": { + "ignoreTimeRange": false + }, + "units": { + "unit": "BYTES" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + }, + { + "title": "Payload packets per Fluent Bit chunk", + "layout": { + "column": 9, + "row": 7, + "width": 4, + "height": 3 + }, + "linkedEntityGuids": null, + "visualization": { + "id": "viz.line" + }, + "rawConfiguration": { + "facet": { + "showOtherSeries": false + }, + "legend": { + "enabled": true + }, + "nrqlQueries": [ + { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT min(logs.fb.payload.count) AS 'Minimum', average(logs.fb.payload.count) AS 'Average', max(logs.fb.payload.count) AS 'Maximum' FROM Metric timeseries MAX " + } + ], + "platformOptions": { + "ignoreTimeRange": false + }, + "units": { + "unit": "COUNT" + }, + "yAxisLeft": { + "zero": true + }, + "yAxisRight": { + "zero": true + } + } + } + ] + } + ], + "variables": [ + { + "name": "cluster_name", + "items": null, + "defaultValues": [ + { + "value": { + "string": "*" + } + } + ], + "nrqlQuery": { + "accountIds": [ + YOUR_ACCOUNT_ID + ], + "query": "SELECT uniques(cluster_name) FROM Metric where metricName = 'fluentbit_input_storage_overlimit'" + }, + "options": { + "ignoreTimeRange": false + }, + "title": "Cluster Name", + "type": "NRQL", + "isMultiSelection": true, + "replacementStrategy": "STRING" + } + ] +} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/NOTES.txt new file mode 100644 index 000000000..289f2157f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{- if (include "newrelic-logging.areValuesValid" .) }} +Your deployment of the New Relic Kubernetes Logging is complete. You can check on the progress of this by running the following command: + + kubectl get daemonset -o wide -w --namespace {{ .Release.Namespace }} {{ template "newrelic-logging.fullname" . }} +{{- else -}} +############################################################################## +#### ERROR: You did not set a license key. #### +############################################################################## + +This deployment will be incomplete until you get your API key from New Relic. + +Then run: + + helm upgrade {{ .Release.Name }} \ + --set licenseKey=(your-license-key) \ + newrelic/newrelic-logging + +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/_helpers.tpl new file mode 100644 index 000000000..439d25cae --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/_helpers.tpl @@ -0,0 +1,215 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "newrelic-logging.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 "newrelic-logging.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if ne $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + + +{{/* Generate basic labels */}} +{{- define "newrelic-logging.labels" }} +app: {{ template "newrelic-logging.name" . }} +chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} +heritage: {{.Release.Service }} +release: {{.Release.Name }} +app.kubernetes.io/name: {{ template "newrelic-logging.name" . }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "newrelic-logging.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{/* +Create the name of the fluent bit config +*/}} +{{- define "newrelic-logging.fluentBitConfig" -}} +{{ template "newrelic-logging.fullname" . }}-fluent-bit-config +{{- end -}} + +{{/* +Return the licenseKey +*/}} +{{- define "newrelic-logging.licenseKey" -}} +{{- if .Values.global}} + {{- if .Values.global.licenseKey }} + {{- .Values.global.licenseKey -}} + {{- else -}} + {{- .Values.licenseKey | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.licenseKey | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the cluster name +*/}} +{{- define "newrelic-logging.cluster" -}} +{{- if .Values.global}} + {{- if .Values.global.cluster }} + {{- .Values.global.cluster -}} + {{- else -}} + {{- .Values.cluster | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.cluster | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the customSecretName +*/}} +{{- define "newrelic-logging.customSecretName" -}} +{{- if .Values.global }} + {{- if .Values.global.customSecretName }} + {{- .Values.global.customSecretName -}} + {{- else -}} + {{- .Values.customSecretName | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.customSecretName | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the customSecretLicenseKey +*/}} +{{- define "newrelic-logging.customSecretKey" -}} +{{- if .Values.global }} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- else -}} + {{- if .Values.global.customSecretKey }} + {{- .Values.global.customSecretKey -}} + {{- else -}} + {{- .Values.customSecretKey | default "" -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.customSecretLicenseKey }} + {{- .Values.customSecretLicenseKey -}} + {{- else -}} + {{- .Values.customSecretKey | default "" -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Returns nrStaging +*/}} +{{- define "newrelic.nrStaging" -}} +{{- if .Values.global }} + {{- if .Values.global.nrStaging }} + {{- .Values.global.nrStaging -}} + {{- end -}} +{{- else if .Values.nrStaging }} + {{- .Values.nrStaging -}} +{{- end -}} +{{- end -}} + +{{/* +Returns fargate +*/}} +{{- define "newrelic.fargate" -}} +{{- if .Values.global }} + {{- if .Values.global.fargate }} + {{- .Values.global.fargate -}} + {{- end -}} +{{- else if .Values.fargate }} + {{- .Values.fargate -}} +{{- end -}} +{{- end -}} + +{{/* +Returns lowDataMode +*/}} +{{- define "newrelic-logging.lowDataMode" -}} +{{/* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{/* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic-logging.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{/* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Returns logsEndpoint +*/}} +{{- define "newrelic-logging.logsEndpoint" -}} +{{- if (include "newrelic.nrStaging" .) -}} +https://staging-log-api.newrelic.com/log/v1 +{{- else if .Values.endpoint -}} +{{ .Values.endpoint -}} +{{- else if eq (substr 0 2 (include "newrelic-logging.licenseKey" .)) "eu" -}} +https://log-api.eu.newrelic.com/log/v1 +{{- else -}} +https://log-api.newrelic.com/log/v1 +{{- end -}} +{{- end -}} + +{{/* +Returns metricsHost +*/}} +{{- define "newrelic-logging.metricsHost" -}} +{{- if (include "newrelic.nrStaging" .) -}} +staging-metric-api.newrelic.com +{{- else if eq (substr 0 2 (include "newrelic-logging.licenseKey" .)) "eu" -}} +metric-api.eu.newrelic.com +{{- else -}} +metric-api.newrelic.com +{{- end -}} +{{- end -}} + +{{/* +Returns if the template should render, it checks if the required values are set. +*/}} +{{- define "newrelic-logging.areValuesValid" -}} +{{- $licenseKey := include "newrelic-logging.licenseKey" . -}} +{{- $customSecretName := include "newrelic-logging.customSecretName" . -}} +{{- $customSecretKey := include "newrelic-logging.customSecretKey" . -}} +{{- and (or $licenseKey (and $customSecretName $customSecretKey))}} +{{- end -}} + +{{/* +If additionalEnvVariables is set, renames to extraEnv. Returns extraEnv. +*/}} +{{- define "newrelic-logging.extraEnv" -}} +{{- if .Values.fluentBit }} + {{- if .Values.fluentBit.additionalEnvVariables }} + {{- toYaml .Values.fluentBit.additionalEnvVariables -}} + {{- else if .Values.fluentBit.extraEnv }} + {{- toYaml .Values.fluentBit.extraEnv -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrole.yaml new file mode 100644 index 000000000..b36340fe6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrole.yaml @@ -0,0 +1,23 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }} +rules: + - apiGroups: [""] + resources: + - namespaces + - pods + verbs: ["get", "list", "watch"] +{{- if .Values.rbac.pspEnabled }} + - apiGroups: + - extensions + resources: + - podsecuritypolicies + resourceNames: + - privileged-{{ template "newrelic-logging.fullname" . }} + verbs: + - use +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..6b258f697 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/clusterrolebinding.yaml @@ -0,0 +1,15 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "newrelic-logging.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/configmap.yaml new file mode 100644 index 000000000..4b1d89014 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/configmap.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ .Release.Namespace }} + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fluentBitConfig" . }} +data: + fluent-bit.conf: | + {{- if .Values.fluentBit.config.service }} + {{- .Values.fluentBit.config.service | nindent 4 }} + {{- end }} + {{- if .Values.fluentBit.config.inputs }} + {{- .Values.fluentBit.config.inputs | nindent 4 }} + {{- end }} + {{- if .Values.fluentBit.config.extraInputs }} + {{- .Values.fluentBit.config.extraInputs | nindent 4}} + {{- end }} + {{- if and (include "newrelic-logging.lowDataMode" .) (.Values.fluentBit.config.lowDataModeFilters) }} + {{- .Values.fluentBit.config.lowDataModeFilters | nindent 4 }} + {{- else }} + {{- .Values.fluentBit.config.filters | nindent 4 }} + {{- end }} + {{- if .Values.fluentBit.config.extraFilters }} + {{- .Values.fluentBit.config.extraFilters | nindent 4}} + {{- end }} + {{- if .Values.fluentBit.config.outputs }} + {{- .Values.fluentBit.config.outputs | nindent 4 }} + {{- end }} + {{- if .Values.fluentBit.config.extraOutputs }} + {{- .Values.fluentBit.config.extraOutputs | nindent 4}} + {{- end }} + {{- if and (.Values.fluentBit.sendMetrics) (.Values.fluentBit.config.metricInstrumentation) }} + {{- .Values.fluentBit.config.metricInstrumentation | nindent 4}} + {{- end }} + parsers.conf: | + {{- if .Values.fluentBit.config.parsers }} + {{- .Values.fluentBit.config.parsers | nindent 4}} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset-windows.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset-windows.yaml new file mode 100644 index 000000000..6a3145d13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset-windows.yaml @@ -0,0 +1,174 @@ +{{- if and (include "newrelic-logging.areValuesValid" $) $.Values.enableWindows }} +{{- range .Values.windowsOsList }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + namespace: {{ $.Release.Namespace }} + labels: + kubernetes.io/os: windows +{{ include "newrelic-logging.labels" $ | indent 4 }} + name: {{ template "newrelic-logging.fullname" $ }}-windows-{{ .version }} + annotations: + {{- if $.Values.daemonSet.annotations }} +{{ toYaml $.Values.daemonSet.annotations | indent 4 }} + {{- end }} +spec: + updateStrategy: + type: {{ $.Values.updateStrategy }} + selector: + matchLabels: + app: {{ template "newrelic-logging.name" $ }} + release: {{ $.Release.Name }} + kubernetes.io/os: windows + template: + metadata: + annotations: + checksum/fluent-bit-config: {{ include (print $.Template.BasePath "/configmap.yaml") $ | sha256sum }} + {{- if $.Values.podAnnotations }} +{{ toYaml $.Values.podAnnotations | indent 8}} + {{- end }} + labels: + app: {{ template "newrelic-logging.name" $ }} + release: {{ $.Release.Name }} + kubernetes.io/os: windows + app.kubernetes.io/name: {{ template "newrelic-logging.name" $ }} + {{- if $.Values.podLabels}} +{{ toYaml $.Values.podLabels | indent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" $ }} + {{- with include "newrelic.common.dnsConfig" $ }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + dnsPolicy: ClusterFirst + terminationGracePeriodSeconds: 10 + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list $.Values.image.pullSecrets) "context" $) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- if $.Values.hostNetwork }} + hostNetwork: {{ $.Values.hostNetwork }} + {{- end }} + {{- if $.Values.windows.initContainers }} + initContainers: +{{ toYaml $.Values.windows.initContainers | indent 8 }} + {{- end }} + containers: + - name: {{ template "newrelic-logging.name" $ }} + # We have to use 'replace' to remove the double-quotes that "newrelic.common.images.image" has, so that + # we can append the Windows image tag suffix (and then re-quote that value) + image: "{{ include "newrelic.common.images.image" ( dict "imageRoot" $.Values.image "context" $) | replace "\"" ""}}-{{ .imageTagSuffix }}" + imagePullPolicy: "{{ $.Values.image.pullPolicy }}" + securityContext: {} + env: + - name: ENDPOINT + value: {{ include "newrelic-logging.logsEndpoint" $ | quote }} + - name: SOURCE + value: {{ if (include "newrelic-logging.lowDataMode" $) }} "k8s" {{- else }} "kubernetes" {{- end }} + - name: LICENSE_KEY + valueFrom: + secretKeyRef: + {{- if (include "newrelic-logging.licenseKey" $) }} + name: {{ template "newrelic-logging.fullname" $ }}-config + key: license + {{- else }} + name: {{ include "newrelic-logging.customSecretName" $ }} + key: {{ include "newrelic-logging.customSecretKey" $ }} + {{- end }} + - name: CLUSTER_NAME + value: {{ include "newrelic-logging.cluster" $ }} + - name: LOG_LEVEL + value: {{ $.Values.fluentBit.logLevel | quote }} + - name: LOG_PARSER + {{- if $.Values.fluentBit.criEnabled }} + value: "cri,docker" + {{- else }} + value: "docker,cri" + {{- end }} + {{- if or (not $.Values.fluentBit.persistence) (eq $.Values.fluentBit.persistence.mode "hostPath") }} + - name: FB_DB + value: {{ $.Values.fluentBit.windowsDb | quote }} + {{- else }} + - name: FB_DB + value: "" + {{- end }} + - name: PATH + value: {{ $.Values.fluentBit.windowsPath | quote }} + - name: K8S_BUFFER_SIZE + value: {{ $.Values.fluentBit.k8sBufferSize | quote }} + - name: K8S_LOGGING_EXCLUDE + value: {{ $.Values.fluentBit.k8sLoggingExclude | default "false" | quote }} + - name: LOW_DATA_MODE + value: {{ include "newrelic-logging.lowDataMode" $ | default "false" | quote }} + - name: RETRY_LIMIT + value: {{ $.Values.fluentBit.retryLimit | quote }} + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SEND_OUTPUT_PLUGIN_METRICS + value: {{ $.Values.fluentBit.sendMetrics | default "false" | quote }} + - name: METRICS_HOST + value: {{ include "newrelic-logging.metricsHost" $ | quote }} + {{- include "newrelic-logging.extraEnv" $ | nindent 12 }} + command: + - C:\fluent-bit\bin\fluent-bit.exe + - -c + - c:\fluent-bit\etc\fluent-bit.conf + - -e + - C:\fluent-bit\bin\out_newrelic.dll + {{- if $.Values.exposedPorts }} + ports: {{ toYaml $.Values.exposedPorts | nindent 12 }} + {{- end }} + volumeMounts: + - mountPath: C:\fluent-bit\etc + name: fluent-bit-config + - mountPath: C:\var\log + name: logs + {{- if and ($.Values.fluentBit.persistence) (ne $.Values.fluentBit.persistence.mode "hostPath") }} + readOnly: true + {{- end }} + # We need to also mount this because the logs in C:\var\logs are actually symlinks to C:\ProgramData. + # So, in order to be able to read these logs, the reading process needs to also have access to C:\ProgramData. + - mountPath: C:\ProgramData + name: progdata + {{- if and ($.Values.fluentBit.persistence) (ne $.Values.fluentBit.persistence.mode "hostPath") }} + readOnly: true + {{- end }} + {{- if $.Values.resources }} + resources: +{{ toYaml $.Values.resources | indent 12 }} + {{- end }} + volumes: + - name: fluent-bit-config + configMap: + name: {{ template "newrelic-logging.fluentBitConfig" $ }} + - name: logs + hostPath: + path: C:\var\log + - name: progdata + hostPath: + path: C:\ProgramData + {{- if $.Values.priorityClassName }} + priorityClassName: {{ $.Values.priorityClassName }} + {{- end }} + nodeSelector: + {{- if $.Values.windowsNodeSelector }} +{{ toYaml $.Values.windowsNodeSelector | indent 8 }} + {{- else }} + kubernetes.io/os: windows + # Windows containers can only be executed on hosts running the exact same Windows version and build number + node.kubernetes.io/windows-build: {{ .buildNumber }} + {{- end }} + {{- if $.Values.tolerations }} + tolerations: +{{ toYaml $.Values.tolerations | indent 8 }} + {{- end }} +--- +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset.yaml new file mode 100644 index 000000000..1e087b6cb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/daemonset.yaml @@ -0,0 +1,212 @@ +{{- if and (include "newrelic-logging.areValuesValid" .) .Values.enableLinux }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + namespace: {{ .Release.Namespace }} + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }} + annotations: + {{- if .Values.daemonSet.annotations }} +{{ toYaml .Values.daemonSet.annotations | indent 4 }} + {{- end }} +spec: + updateStrategy: + type: {{ .Values.updateStrategy }} + selector: + matchLabels: + app: {{ template "newrelic-logging.name" . }} + release: {{.Release.Name }} + template: + metadata: + annotations: + checksum/fluent-bit-config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- if .Values.podAnnotations }} +{{ toYaml .Values.podAnnotations | indent 8}} + {{- end }} + labels: + app: {{ template "newrelic-logging.name" . }} + release: {{.Release.Name }} + kubernetes.io/os: linux + app.kubernetes.io/name: {{ template "newrelic-logging.name" . }} + {{- if .Values.podLabels}} +{{ toYaml .Values.podLabels | indent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + dnsPolicy: ClusterFirstWithHostNet + terminationGracePeriodSeconds: 10 + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.image.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + {{- if .Values.hostNetwork }} + hostNetwork: {{ .Values.hostNetwork }} + {{- end }} + initContainers: + {{- if and (.Values.fluentBit.persistence) (eq .Values.fluentBit.persistence.mode "persistentVolume") }} + - name: init + image: busybox:1.36 + command: ["/bin/sh", "-c"] + args: ["/bin/find /db -type f -mtime +1 -delete"] # Delete all db files not updated in the last 24h + volumeMounts: + - name: fb-db-pvc + mountPath: /db + {{- end }} + {{- if .Values.initContainers }} +{{ toYaml .Values.initContainers | indent 8 }} + {{- end }} + containers: + - name: {{ template "newrelic-logging.name" . }} + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 12 }} + {{- end }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + env: + - name: ENDPOINT + value: {{ include "newrelic-logging.logsEndpoint" . | quote }} + - name: SOURCE + value: {{ if (include "newrelic-logging.lowDataMode" .) }} "k8s" {{- else }} "kubernetes" {{- end }} + - name: LICENSE_KEY + valueFrom: + secretKeyRef: + {{- if (include "newrelic-logging.licenseKey" .) }} + name: {{ template "newrelic-logging.fullname" . }}-config + key: license + {{- else }} + name: {{ include "newrelic-logging.customSecretName" . }} + key: {{ include "newrelic-logging.customSecretKey" . }} + {{- end }} + - name: CLUSTER_NAME + value: {{ include "newrelic-logging.cluster" . }} + - name: LOG_LEVEL + value: {{ .Values.fluentBit.logLevel | quote }} + - name: LOG_PARSER + {{- if .Values.fluentBit.criEnabled }} + value: "cri,docker" + {{- else }} + value: "docker,cri" + {{- end }} + # NODE_NAME needs to be defined before FB_DB, because FB_DB references NODE_NAME in its value when using persistentVolume + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + {{- if or (not .Values.fluentBit.persistence) (eq .Values.fluentBit.persistence.mode "hostPath") }} + - name: FB_DB + value: {{ .Values.fluentBit.db | quote }} + {{- else if eq .Values.fluentBit.persistence.mode "persistentVolume" }} + - name: FB_DB + value: "/db/$(NODE_NAME)-fb.db" + {{- else }} + - name: FB_DB + value: "" + {{- end }} + - name: PATH + value: {{ .Values.fluentBit.path | quote }} + - name: K8S_BUFFER_SIZE + value: {{ .Values.fluentBit.k8sBufferSize | quote }} + - name: K8S_LOGGING_EXCLUDE + value: {{ .Values.fluentBit.k8sLoggingExclude | default "false" | quote }} + - name: LOW_DATA_MODE + value: {{ include "newrelic-logging.lowDataMode" . | default "false" | quote }} + - name: RETRY_LIMIT + value: {{ .Values.fluentBit.retryLimit | quote }} + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SEND_OUTPUT_PLUGIN_METRICS + value: {{ $.Values.fluentBit.sendMetrics | default "false" | quote }} + - name: METRICS_HOST + value: {{ include "newrelic-logging.metricsHost" . | quote }} + {{- include "newrelic-logging.extraEnv" . | nindent 12 }} + command: + - /fluent-bit/bin/fluent-bit + - -c + - /fluent-bit/etc/fluent-bit.conf + - -e + - /fluent-bit/bin/out_newrelic.so + volumeMounts: + - name: fluent-bit-config + mountPath: /fluent-bit/etc + - name: logs + # We mount /var by default because container logs could be on /var/log or /var/lib/docker/containers (symlinked to /var/log) + mountPath: {{ .Values.fluentBit.linuxMountPath | default "/var" }} + {{- if and (.Values.fluentBit.persistence) (ne .Values.fluentBit.persistence.mode "hostPath") }} + readOnly: true + {{- end }} + {{- if and (.Values.fluentBit.persistence) (eq .Values.fluentBit.persistence.mode "persistentVolume") }} + - name: fb-db-pvc + mountPath: /db + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.exposedPorts }} + ports: {{ toYaml .Values.exposedPorts | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- end }} + volumes: + - name: fluent-bit-config + configMap: + name: {{ template "newrelic-logging.fluentBitConfig" . }} + - name: logs + hostPath: + path: {{ .Values.fluentBit.linuxMountPath | default "/var" }} + {{- if and (.Values.fluentBit.persistence) (eq .Values.fluentBit.persistence.mode "persistentVolume") }} + - name: fb-db-pvc + persistentVolumeClaim: + {{- if .Values.fluentBit.persistence.persistentVolume.existingVolumeClaim }} + claimName: {{ .Values.fluentBit.persistence.persistentVolume.existingVolumeClaim }} + {{- else }} + claimName: {{ template "newrelic-logging.fullname" . }}-pvc + {{- end }} + {{- end }} + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- if $.Values.priorityClassName }} + priorityClassName: {{ $.Values.priorityClassName }} + {{- end }} + {{- if .Values.nodeAffinity }} + affinity: + nodeAffinity: {{ .Values.nodeAffinity | toYaml | nindent 10 }} + {{- else if include "newrelic.fargate" . }} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: eks.amazonaws.com/compute-type + operator: NotIn + values: + - fargate + {{- end }} + nodeSelector: + {{- if .Values.nodeSelector }} +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- else if $.Values.enableWindows }} + # We add this only if Windows is enabled to keep backwards-compatibility. Prior to version 1.14, this label was + # named beta.kubernetes.io/os. In version 1.14, it was deprecated and replaced by this one. Version 1.14 also + # introduces Windows support. Therefore, anyone wishing to use Windows containers must bet at version >=1.14 and + # are going to need this label, in order to avoid placing a linux container on a windows node, and vice-versa. + kubernetes.io/os: linux + {{- end }} + {{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/persistentvolume.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/persistentvolume.yaml new file mode 100644 index 000000000..f2fb93d77 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/persistentvolume.yaml @@ -0,0 +1,57 @@ +{{- if (not (empty .Values.fluentBit.persistence)) }} + +{{- if and (eq .Values.fluentBit.persistence.mode "persistentVolume") (not .Values.fluentBit.persistence.persistentVolume.storageClass) (not .Values.fluentBit.persistence.persistentVolume.existingVolumeClaim) }} +{{ fail "You should provide a ReadWriteMany storageClass or an existingVolumeClaim if using persitentVolume as Fluent Bit persistence mode." }} +{{- end }} + +{{- if and (eq .Values.fluentBit.persistence.mode "persistentVolume") (not .Values.fluentBit.persistence.persistentVolume.existingVolumeClaim) }} +{{- if and (not .Values.fluentBit.persistence.persistentVolume.dynamicProvisioning) (not .Values.fluentBit.persistence.persistentVolume.existingVolume) }} +apiVersion: v1 +kind: PersistentVolume +metadata: + namespace: {{ .Release.Namespace }} + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }}-pv + annotations: + {{- if .Values.fluentBit.persistence.persistentVolume.annotations.volume }} +{{ toYaml .Values.fluentBit.persistence.persistentVolume.annotations.volume | indent 4 }} + {{- end }} +spec: + accessModes: + - ReadWriteMany + capacity: + storage: {{ .Values.fluentBit.persistence.persistentVolume.size }} + storageClassName: {{ .Values.fluentBit.persistence.persistentVolume.storageClass }} + persistentVolumeReclaimPolicy: Delete + {{- if .Values.fluentBit.persistence.persistentVolume.extra.volume }} +{{ toYaml .Values.fluentBit.persistence.persistentVolume.extra.volume | indent 2 }} + {{- end }} +--- +{{- end }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + namespace: {{ .Release.Namespace }} + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }}-pvc + annotations: + {{- if .Values.fluentBit.persistence.persistentVolume.annotations.claim }} +{{ toYaml .Values.fluentBit.persistence.persistentVolume.annotations.claim | indent 4 }} + {{- end }} +spec: + storageClassName: {{ .Values.fluentBit.persistence.persistentVolume.storageClass }} + accessModes: + - ReadWriteMany +{{- if .Values.fluentBit.persistence.persistentVolume.existingVolume }} + volumeName: {{ .Values.fluentBit.persistence.persistentVolume.existingVolume }} +{{- else if not .Values.fluentBit.persistence.persistentVolume.dynamicProvisioning }} + volumeName: {{ template "newrelic-logging.fullname" . }}-pv +{{- end }} + resources: + requests: + storage: {{ .Values.fluentBit.persistence.persistentVolume.size }} + {{- if .Values.fluentBit.persistence.persistentVolume.extra.claim }} +{{ toYaml .Values.fluentBit.persistence.persistentVolume.extra.claim | indent 2 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/podsecuritypolicy.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..2c8c598e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/podsecuritypolicy.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: privileged-{{ template "newrelic-logging.fullname" . }} +spec: + allowedCapabilities: + - '*' + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - '*' + hostPID: true + hostIPC: true + hostPorts: + - min: 1 + max: 65536 +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/secret.yaml new file mode 100644 index 000000000..47a56e573 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- $licenseKey := include "newrelic-logging.licenseKey" . -}} +{{- if $licenseKey }} +apiVersion: v1 +kind: Secret +metadata: + namespace: {{ .Release.Namespace }} + labels: {{ include "newrelic-logging.labels" . | indent 4 }} + name: {{ template "newrelic-logging.fullname" . }}-config +type: Opaque +data: + license: {{ $licenseKey | b64enc }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/serviceaccount.yaml new file mode 100644 index 000000000..51da56a3e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if include "newrelic.common.serviceAccount.annotations" . }} + annotations: + {{- include "newrelic.common.serviceAccount.annotations" . | nindent 4 }} + {{- end }} + labels: + app: {{ template "newrelic-logging.name" . }} + chart: {{ template "newrelic-logging.chart" . }} + heritage: "{{ .Release.Service }}" + release: "{{ .Release.Name }}" + {{- /*include "newrelic.common.labels" . | nindent 4 /!\ Breaking change /!\ */}} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/cri_parser_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/cri_parser_test.yaml new file mode 100644 index 000000000..f4a1d01d0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/cri_parser_test.yaml @@ -0,0 +1,37 @@ +suite: test cri, docker parser options in daemonsets +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: cri enabled by default and docker as fallback + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: LOG_PARSER + value: "cri,docker" + - it: docker is set if enabled by and cri as fallback + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + fluentBit: + criEnabled: false + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: LOG_PARSER + value: "docker,cri" \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/dns_config_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/dns_config_test.yaml new file mode 100644 index 000000000..76d24eac5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/dns_config_test.yaml @@ -0,0 +1,62 @@ +suite: test dnsConfig options in daemonsets +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: daemonsets contain dnsConfig block when provided + set: + licenseKey: nr_license_key + enableWindows: true + dnsConfig: + nameservers: + - 192.0.2.1 + asserts: + - exists: + path: spec.template.spec.dnsConfig + template: templates/daemonset.yaml + - exists: + path: spec.template.spec.dnsConfig + template: templates/daemonset-windows.yaml + + - it: daemonsets do not contain dnsConfig block when not provided + set: + licenseKey: nr_license_key + enableWindows: true + dnsConfig: {} + asserts: + - notExists: + path: spec.template.spec.dnsConfig + template: templates/daemonset.yaml + - notExists: + path: spec.template.spec.dnsConfig + template: templates/daemonset-windows.yaml + + - it: daemonsets contain provided dnsConfig options + set: + licenseKey: nr_license_key + enableWindows: true + dnsConfig: + options: + - name: ndots + value: "1" + asserts: + - equal: + path: spec.template.spec.dnsConfig.options[0].name + value: ndots + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.dnsConfig.options[0].value + value: "1" + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.dnsConfig.options[0].name + value: ndots + template: templates/daemonset-windows.yaml + - equal: + path: spec.template.spec.dnsConfig.options[0].value + value: "1" + template: templates/daemonset-windows.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml new file mode 100644 index 000000000..82e700d93 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/endpoint_region_selection_test.yaml @@ -0,0 +1,128 @@ +suite: test endpoint selection based on region settings +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: endpoint-selection-release + namespace: endpoint-selection-namespace +tests: + + - it: selects staging endpoints if nrStaging is enabled + set: + licenseKey: nr_license_key + nrStaging: true + enableWindows: true + asserts: + # Linux + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://staging-log-api.newrelic.com/log/v1" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "staging-metric-api.newrelic.com" + template: templates/daemonset.yaml + # Windows + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://staging-log-api.newrelic.com/log/v1" + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "staging-metric-api.newrelic.com" + template: templates/daemonset-windows.yaml + + - it: selects US endpoints for a US license key + set: + licenseKey: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaFFFFNRAL + enableWindows: true + asserts: + # Linux + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://log-api.newrelic.com/log/v1" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.newrelic.com" + template: templates/daemonset.yaml + # Windows + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://log-api.newrelic.com/log/v1" + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.newrelic.com" + template: templates/daemonset-windows.yaml + + - it: selects EU endpoints for a EU license key + set: + licenseKey: euaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaFFFFNRAL + enableWindows: true + asserts: + # Linux + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://log-api.eu.newrelic.com/log/v1" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.eu.newrelic.com" + template: templates/daemonset.yaml + # Windows + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "https://log-api.eu.newrelic.com/log/v1" + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.eu.newrelic.com" + template: templates/daemonset-windows.yaml + + + - it: selects custom logs endpoint if provided + set: + licenseKey: euaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaFFFFNRAL + endpoint: custom + enableWindows: true + asserts: + # Linux + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "custom" + template: templates/daemonset.yaml + # Windows + - contains: + path: spec.template.spec.containers[0].env + content: + name: ENDPOINT + value: "custom" + template: templates/daemonset-windows.yaml \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml new file mode 100644 index 000000000..446f829b0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_k8logging_exclude_test.yaml @@ -0,0 +1,45 @@ +suite: test fluent-bit exclude logging +templates: + - templates/daemonset.yaml + - templates/configmap.yaml + - templates/persistentvolume.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: K8S_LOGGING_EXCLUDE set true + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + fluentBit: + k8sLoggingExclude: true + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="K8S_LOGGING_EXCLUDE")].value + value: "true" + template: templates/daemonset.yaml + - it: K8S_LOGGING_EXCLUDE set false + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + fluentBit: + k8sLoggingExclude: false + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="K8S_LOGGING_EXCLUDE")].value + value: "false" + template: templates/daemonset.yaml + - it: K8S_LOGGING_EXCLUDE set value xyz and expect it to be set + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + fluentBit: + k8sLoggingExclude: xyz + asserts: + - equal: + path: spec.template.spec.containers[0].env[?(@.name=="K8S_LOGGING_EXCLUDE")].value + value: "xyz" + template: templates/daemonset.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml new file mode 100644 index 000000000..67d14c795 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_persistence_test.yaml @@ -0,0 +1,317 @@ +suite: test fluent-bit persistence options +templates: + - templates/daemonset.yaml + - templates/configmap.yaml + - templates/persistentvolume.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: default persistence is hostPath, DB is set properly and logs volume is read/write + set: + licenseKey: nr_license_key + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: logs + mountPath: /var + template: templates/daemonset.yaml + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: fb-db-pvc + mountPath: /db + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.volumes + content: + name: logs + hostPath: + path: /var + template: templates/daemonset.yaml + - notContains: + path: spec.template.spec.volumes + content: + name: fb-db-pvc + persistentVolumeClaim: + claimName: my-release-newrelic-logging-pvc + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: FB_DB + value: /var/log/flb_kube.db + template: templates/daemonset.yaml + - hasDocuments: + count: 0 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence set to none should keep FB_DB env empty and mount logs volume as read-only + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: none + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: FB_DB + value: "" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: logs + mountPath: /var + readOnly: true + template: templates/daemonset.yaml + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: fb-db-pvc + mountPath: /db + template: templates/daemonset.yaml + - notContains: + path: spec.template.spec.volumes + content: + name: fb-db-pvc + persistentVolumeClaim: + claimName: my-release-newrelic-logging-pvc + template: templates/daemonset.yaml + - hasDocuments: + count: 0 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence set to persistentVolume should create volume, add it to daemonset, add an initContainer to cleanup and set the FB_DB. Dynamic provisioning is enabled by default. + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: FB_DB + value: "/db/$(NODE_NAME)-fb.db" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: logs + mountPath: /var + readOnly: true + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: fb-db-pvc + mountPath: /db + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.volumes + content: + name: fb-db-pvc + persistentVolumeClaim: + claimName: my-release-newrelic-logging-pvc + template: templates/daemonset.yaml + - isNotNullOrEmpty: + path: spec.template.spec.initContainers + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.initContainers[0].volumeMounts + content: + name: fb-db-pvc + mountPath: /db + template: templates/daemonset.yaml + - hasDocuments: + count: 1 + template: templates/persistentvolume.yaml + - isKind: + of: PersistentVolumeClaim + template: templates/persistentvolume.yaml + - equal: + path: spec.accessModes + value: + - ReadWriteMany + template: templates/persistentvolume.yaml + - it: fluentBit.persistence.persistentVolume with non dynamic provisioning should create the PV and PVC + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + dynamicProvisioning: false + asserts: + - hasDocuments: + count: 2 + template: templates/persistentvolume.yaml + - isKind: + of: PersistentVolume + documentIndex: 0 + template: templates/persistentvolume.yaml + - isKind: + of: PersistentVolumeClaim + documentIndex: 1 + template: templates/persistentvolume.yaml + - equal: + path: spec.accessModes + value: + - ReadWriteMany + documentIndex: 0 + template: templates/persistentvolume.yaml + - equal: + path: spec.accessModes + value: + - ReadWriteMany + documentIndex: 1 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence storage class should be set properly on PV and PVC + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + dynamicProvisioning: false + storageClass: sample-storage-rwx + asserts: + - equal: + path: spec.storageClassName + value: sample-storage-rwx + documentIndex: 0 + template: templates/persistentvolume.yaml + - equal: + path: spec.storageClassName + value: sample-storage-rwx + documentIndex: 1 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence.persistentVolume size should be set properly on PV and PVC + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + dynamicProvisioning: false + size: 100Gi + asserts: + - equal: + path: spec.capacity.storage + value: 100Gi + documentIndex: 0 + template: templates/persistentvolume.yaml + - equal: + path: spec.resources.requests.storage + value: 100Gi + documentIndex: 1 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence.persistentVolume not dynamic provisioned but volumeName provided should use the volumeName and do not create a PV + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + dynamicProvisioning: false + existingVolume: existing-volume + asserts: + - hasDocuments: + count: 1 + template: templates/persistentvolume.yaml + - isKind: + of: PersistentVolumeClaim + template: templates/persistentvolume.yaml + - equal: + path: spec.volumeName + value: existing-volume + template: templates/persistentvolume.yaml + - it: fluentBit.persistence.persistentVolume if a existing claim is provided it's used and PV/PVC are not created + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + dynamicProvisioning: false + existingVolumeClaim: existing-claim + asserts: + - hasDocuments: + count: 0 + template: templates/persistentvolume.yaml + - contains: + path: spec.template.spec.volumes + content: + name: fb-db-pvc + persistentVolumeClaim: + claimName: existing-claim + template: templates/daemonset.yaml + - it: fluentBit.persistence.persistentVolume annotations for PV and PVC are used + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + annotations: + volume: + foo: bar + claim: + baz: qux + dynamicProvisioning: false + asserts: + - equal: + path: metadata.annotations.foo + value: bar + documentIndex: 0 + template: templates/persistentvolume.yaml + - equal: + path: metadata.annotations.baz + value: qux + documentIndex: 1 + template: templates/persistentvolume.yaml + - it: fluentBit.persistence.persistentVolume extra for PV and PVC are used + set: + licenseKey: nr_license_key + fluentBit: + persistence: + mode: persistentVolume + persistentVolume: + storageClass: sample-rwx + extra: + volume: + nfs: + path: /tmp/ + server: 1.1.1.1 + claim: + some: property + dynamicProvisioning: false + asserts: + - equal: + path: spec.nfs + value: + path: /tmp/ + server: 1.1.1.1 + documentIndex: 0 + template: templates/persistentvolume.yaml + - equal: + path: spec.some + value: property + documentIndex: 1 + template: templates/persistentvolume.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml new file mode 100644 index 000000000..86edd7ccd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_pod_label_test.yaml @@ -0,0 +1,48 @@ +suite: test fluent-bit pods labels +templates: + - templates/daemonset.yaml + - templates/configmap.yaml + - templates/persistentvolume.yaml +release: + name: my-release + namespace: my-namespace +tests: +- it: multiple pod labels are set properly + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + podLabels: + key1: value1 + key2: value2 + asserts: + - equal: + path: spec.template.metadata.labels.key1 + value: value1 + template: templates/daemonset.yaml + - equal: + path: spec.template.metadata.labels.key2 + value: value2 + template: templates/daemonset.yaml +- it: single pod label set properly + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + podLabels: + key1: value1 + asserts: + - equal: + path: spec.template.metadata.labels.key1 + value: value1 + template: templates/daemonset.yaml +- it: pod labels are not set + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + asserts: + - notExists: + path: spec.template.metadata.labels.key1 + - notExists: + path: spec.template.metadata.labels.key2 \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml new file mode 100644 index 000000000..f320172cb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/fluentbit_sendmetrics_test.yaml @@ -0,0 +1,74 @@ +suite: test fluentbit send metrics +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: sendmetrics-release + namespace: sendmetrics-namespace +tests: + + - it: sets requirement environment variables to send metrics + set: + licenseKey: nr_license_key + enableWindows: true + fluentBit.sendMetrics: true + asserts: + # Linux + - contains: + path: spec.template.spec.containers[0].env + content: + name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEND_OUTPUT_PLUGIN_METRICS + value: "true" + template: templates/daemonset.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.newrelic.com" + template: templates/daemonset.yaml + # Windows + - contains: + path: spec.template.spec.containers[0].env + content: + name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: SEND_OUTPUT_PLUGIN_METRICS + value: "true" + template: templates/daemonset-windows.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: METRICS_HOST + value: "metric-api.newrelic.com" + template: templates/daemonset-windows.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/host_network_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/host_network_test.yaml new file mode 100644 index 000000000..612d1d9a5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/host_network_test.yaml @@ -0,0 +1,46 @@ +suite: test hostNetwork options in fluent-bit pods +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: daemonsets does not contain hostNetwork block when not provided + set: + licenseKey: nr_license_key + enableWindows: true + asserts: + - notExists: + path: spec.template.spec.hostNetwork + template: templates/daemonset.yaml + - notExists: + path: spec.template.spec.hostNetwork + template: templates/daemonset-windows.yaml + - it: daemonsets does not contain hostNetwork block when provided as false + set: + licenseKey: nr_license_key + enableWindows: true + hostNetwork: false + asserts: + - notExists: + path: spec.template.spec.hostNetwork + template: templates/daemonset.yaml + - notExists: + path: spec.template.spec.hostNetwork + template: templates/daemonset-windows.yaml + - it: daemonsets does contain hostNetwork=true when provided as true + set: + licenseKey: nr_license_key + enableWindows: true + hostNetwork: true + asserts: + - equal: + path: spec.template.spec.hostNetwork + value: true + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.hostNetwork + value: true + template: templates/daemonset-windows.yaml \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/images_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/images_test.yaml new file mode 100644 index 000000000..533a367c5 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/images_test.yaml @@ -0,0 +1,96 @@ +suite: test images settings +templates: + - templates/configmap.yaml + - templates/daemonset.yaml + - templates/daemonset-windows.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: image names are correct + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: newrelic/newrelic-fluentbit-output:2.0.2 + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.containers[0].image + value: newrelic/newrelic-fluentbit-output:2.0.2-windows-ltsc-2019 + template: templates/daemonset-windows.yaml + documentIndex: 0 + - equal: + path: spec.template.spec.containers[0].image + value: newrelic/newrelic-fluentbit-output:2.0.2-windows-ltsc-2022 + template: templates/daemonset-windows.yaml + documentIndex: 1 + - it: global registry is used if set + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + global: + images: + registry: global_registry + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: global_registry/.* + - it: local registry overrides global + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + global: + images: + registry: global_registry + image: + registry: local_registry + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: local_registry/.* + - it: pullSecrets is used if defined + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + image: + pullSecrets: + - name: regsecret + asserts: + - equal: + path: spec.template.spec.imagePullSecrets[0].name + value: regsecret + - it: pullSecrets are merged + templates: + - templates/daemonset.yaml + - templates/daemonset-windows.yaml + set: + licenseKey: nr_license_key + enableWindows: true + global: + images: + pullSecrets: + - name: global_regsecret + image: + pullSecrets: + - name: regsecret + asserts: + - equal: + path: spec.template.spec.imagePullSecrets[0].name + value: global_regsecret + - equal: + path: spec.template.spec.imagePullSecrets[1].name + value: regsecret diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/linux_volume_mount_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/linux_volume_mount_test.yaml new file mode 100644 index 000000000..83d2a2c11 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/linux_volume_mount_test.yaml @@ -0,0 +1,37 @@ +suite: test fluent-bit linux mount for logs +templates: + - templates/configmap.yaml + - templates/daemonset.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: is set to /var by default an + set: + licenseKey: nr_license_key + asserts: + - equal: + path: spec.template.spec.containers[0].volumeMounts[1].mountPath + value: /var + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.volumes[1].hostPath.path + value: /var + template: templates/daemonset.yaml + documentIndex: 0 + - it: is set to linuxMountPath if set + templates: + - templates/daemonset.yaml + set: + licenseKey: nr_license_key + fluentBit.linuxMountPath: /var/log + asserts: + - equal: + path: spec.template.spec.containers[0].volumeMounts[1].mountPath + value: /var/log + template: templates/daemonset.yaml + - equal: + path: spec.template.spec.volumes[1].hostPath.path + value: /var/log + template: templates/daemonset.yaml + documentIndex: 0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/rbac_test.yaml new file mode 100644 index 000000000..a8d85da98 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/tests/rbac_test.yaml @@ -0,0 +1,48 @@ +suite: test RBAC creation +templates: + - templates/clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: template rbac if it is configured to do it + set: + rbac.create: true + asserts: + - hasDocuments: + count: 1 + + - it: don't template rbac if it is disabled + set: + rbac.create: false + asserts: + - hasDocuments: + count: 0 + + - it: RBAC points to the service account that is created by default + set: + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: subjects[0].name + value: my-release-newrelic-logging + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: subjects[0].name + value: sa-test + + - it: RBAC points to the default service account when serviceAccount is disabled + set: + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: subjects[0].name + value: default diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/values.yaml new file mode 100644 index 000000000..c5d85b43f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-logging/values.yaml @@ -0,0 +1,361 @@ +# IMPORTANT: Specify your New Relic API key here. +# licenseKey: +# +# Optionally, specify a cluster name and log records can +# be filtered by cluster. +# cluster: +# +# or Specify secret which contains New Relic API key +# customSecretName: secret_name +# customSecretLicenseKey: secret_key +# +# The previous values can also be set as global so that they +# can be shared by other newrelic product's charts +# +# global: +# licenseKey: +# cluster: +# customSecretName: +# customSecretLicenseKey: +# +# IMPORTANT: if you use a kubernetes secret to specify the license, +# you have to manually provide the correct endpoint depending on +# whether your account is for the EU region or not. +# +# endpoint: https://log-api.newrelic.com/log/v1 + +fluentBit: + logLevel: "info" + path: "/var/log/containers/*.log" + linuxMountPath: /var + windowsPath: "C:\\var\\log\\containers\\*.log" + db: "/var/log/flb_kube.db" + windowsDb: "C:\\var\\log\\flb_kube.db" + criEnabled: true + k8sBufferSize: "32k" + k8sLoggingExclude: "false" + retryLimit: 5 + sendMetrics: false + extraEnv: [] + # extraEnv: + # - name: HTTPS_PROXY + # value: http://example.com:3128 + # - name: METADATA_NAME + # valueFrom: + # fieldRef: + # fieldPath: metadata.name + + # Indicates how fluent-bit database is persisted + persistence: + # Define the persistent mode for fluent-bit db, allowed options are `hostPath` (default), `none`, `persistentVolume`. + # - `hostPath` will use hostPath to store the db file on the node disk. + # - `none` will disable the fluent-bit db file, this could cause log duplication or data loss in case fluent-bit gets restarted. + # - `persistentVolume` will use a ReadWriteMany persistent volume to store the db file. This will override `fluentBit.db` path and use `/db/${NODE_NAME}-fb.db` file instead. + mode: "hostPath" + + # In case persistence.mode is set to persistentVolume this will be needed + persistentVolume: + # The storage class should allow ReadWriteMany mode + storageClass: + # Volume and claim size. + size: 10Gi + # If dynamicProvisioning is enabled the chart will create only the PersistentVolumeClaim + dynamicProvisioning: true + # If an existingVolume is provided, we'll use it instead creating a new one + existingVolume: + # If an existingVolumeClaim is provided, we'll use it instead creating a new one + existingVolumeClaim: + # In case you need to add annotations to the created volume or claim + annotations: + volume: {} + claim: {} + # In case you need to specify any other option to your volume or claim + extra: + volume: + # nfs: + # path: /tmp/ + # server: 1.1.1.1 + claim: {} + + + # New Relic default configuration for fluent-bit.conf (service, inputs, filters, outputs) + # and parsers.conf (parsers). The configuration below is not configured for lowDataMode and will + # send all attributes. If custom configuration is required, update these variables. + config: + # Note that Prometheus metric collection needs the HTTP server to be online at port 2020 (see fluentBit.config.metricInstrumentation) + service: | + [SERVICE] + Flush 1 + Log_Level ${LOG_LEVEL} + Daemon off + Parsers_File parsers.conf + HTTP_Server On + HTTP_Listen 0.0.0.0 + HTTP_Port 2020 + + inputs: | + [INPUT] + Name tail + Alias pod-logs-tailer + Tag kube.* + Path ${PATH} + multiline.parser ${LOG_PARSER} + DB ${FB_DB} + Mem_Buf_Limit 7MB + Skip_Long_Lines On + Refresh_Interval 10 + +# extraInputs: | +# [INPUT] +# Name dummy +# Tag dummy.log + + filters: | + [FILTER] + Name kubernetes + Alias kubernetes-enricher + Match kube.* + # We need the full DNS suffix as Windows only supports resolving names with this suffix + # See: https://kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#dns-limitations + Kube_URL https://kubernetes.default.svc.cluster.local:443 + Buffer_Size ${K8S_BUFFER_SIZE} + K8S-Logging.Exclude ${K8S_LOGGING_EXCLUDE} + + [FILTER] + Name record_modifier + Alias node-attributes-enricher + Match * + Record cluster_name "${CLUSTER_NAME}" + +# extraFilters: | +# [FILTER] +# Name grep +# Match * +# Exclude log lvl=debug* + + lowDataModeFilters: | + [FILTER] + Name kubernetes + Match kube.* + Alias kubernetes-enricher + # We need the full DNS suffix as Windows only supports resolving names with this suffix + # See: https://kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#dns-limitations + Kube_URL https://kubernetes.default.svc.cluster.local:443 + Buffer_Size ${K8S_BUFFER_SIZE} + K8S-Logging.Exclude ${K8S_LOGGING_EXCLUDE} + Labels Off + Annotations Off + + [FILTER] + Name nest + Match * + Alias kubernetes-attribute-lifter + Operation lift + Nested_under kubernetes + + [FILTER] + Name record_modifier + Match * + Alias node-attributes-enricher-filter + Record cluster_name "${CLUSTER_NAME}" + Allowlist_key container_name + Allowlist_key namespace_name + Allowlist_key pod_name + Allowlist_key stream + Allowlist_key message + Allowlist_key log + + outputs: | + [OUTPUT] + Name newrelic + Match * + Alias newrelic-logs-forwarder + licenseKey ${LICENSE_KEY} + endpoint ${ENDPOINT} + lowDataMode ${LOW_DATA_MODE} + sendMetrics ${SEND_OUTPUT_PLUGIN_METRICS} + Retry_Limit ${RETRY_LIMIT} + +# extraOutputs: | +# [OUTPUT] +# Name null +# Match * + +# parsers: | +# [PARSER] +# Name my_custom_parser +# Format json +# Time_Key time +# Time_Format %Y-%m-%dT%H:%M:%S.%L +# Time_Keep On + metricInstrumentation: | + [INPUT] + name prometheus_scrape + Alias fb-metrics-collector + host 127.0.0.1 + port 2020 + tag fb_metrics + metrics_path /api/v2/metrics/prometheus + scrape_interval 10s + + [OUTPUT] + Name prometheus_remote_write + Match fb_metrics + Alias fb-metrics-forwarder + Host ${METRICS_HOST} + Port 443 + Uri /prometheus/v1/write?prometheus_server=${CLUSTER_NAME} + Header Authorization Bearer ${LICENSE_KEY} + Tls On + # Windows pods using prometheus_remote_write currently have issues if TLS verify is On + Tls.verify Off + # User-defined labels + add_label app fluent-bit + add_label cluster_name "${CLUSTER_NAME}" + add_label pod_name ${HOSTNAME} + add_label node_name ${NODE_NAME} + add_label source kubernetes + +image: + repository: newrelic/newrelic-fluentbit-output +# registry: my_registry + tag: "" + pullPolicy: IfNotPresent + ## See https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod + pullSecrets: [] +# - name: regsecret + +# By default, the Linux DaemonSet will always be deployed, while the Windows DaemonSet(s) won't. +enableLinux: true +enableWindows: false +# For every entry in this Windows OS list, we will create an independent DaemonSet which will get deployed +# on Windows nodes running each specific Windows version and build number. Note that +# Windows containers can only be executed on hosts running the exact same Windows version and build number, +# because Kubernetes only supports process isolation and not Hyper-V isolation (as of September 2021) +windowsOsList: + # We aim to support (limited to LTSC2019/LTSC2022 using GitHub actions, see https://github.com/actions/runner-images/tree/main/images/win): + # https://kubernetes.io/docs/setup/production-environment/windows/intro-windows-in-kubernetes/#windows-os-version-support + - version: ltsc2019 + imageTagSuffix: windows-ltsc-2019 + buildNumber: 10.0.17763 + - version: ltsc2022 + imageTagSuffix: windows-ltsc-2022 + buildNumber: 10.0.20348 + +# Default set of resources assigned to the DaemonSet pods +resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 250m + memory: 64Mi + +rbac: + # Specifies whether RBAC resources should be created + create: true + pspEnabled: false + +serviceAccount: + # Specifies whether a ServiceAccount should be created + create: + # The name of the ServiceAccount to use. + # If not set and create is true, a name is generated using the fullname template + name: + # Specify any annotations to add to the ServiceAccount + annotations: {} + +# Optionally configure ports to expose metrics on /api/v1/metrics/prometheus +# See - https://docs.fluentbit.io/manual/administration/monitoring +exposedPorts: [] +# - containerPort: 2020 +# hostPort: 2020 +# name: metrics +# protocol: TCP + +# If you wish to provide additional labels to apply to the pod(s), specify +# them here +# podLabels: + +# Pod scheduling priority +# Ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +# priorityClassName: high-priority + +# Node affinity rules +# Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity +# +# IMPORTANT # +# ######### # +# When .Values.global.fargate == true, the chart will automatically add the required affinity rules to exclude +# the DaemonSet from Fargate nodes. There is no need to manually touch this property achieve this. +# This automatic exclusion will, however, not take place if this value is overridden: Setting this to a +# non-empty value WHEN deploying in EKS Fargate (global.fargate == true) requires the user to manually +# include in their custom ruleset an exclusion for nodes with "eks.amazonaws.com/compute-type: fargate", as +# the New Relic DaemonSet MUST NOT be deployed on fargate nodes, as the operator takes care of injecting it +# as a sidecar instead. +# Please refer to the daemonset.yaml template for more details on how to achieve this. +nodeAffinity: {} + +# Node labels for pod assignment +# Ref: https://kubernetes.io/docs/user-guide/node-selection/ +# Note that the Linux DaemonSet already contains a node selector label based on their OS (kubernetes.io/os: linux). +nodeSelector: {} + +# Note that the Windows DaemonSet already contains a node selector label based on their OS (kubernetes.io/os: windows). +# and build number (node.kubernetes.io/windows-build: {{ .buildNumber }}, to ensure that each version of the DaemonSet +# gets deployed only on those Windows nodes running the exact same Windows version and build number. Note that +# Windows containers can only be executed on hosts running the exact same Windows version and build number. +windowsNodeSelector: {} + +# These are default tolerations to be able to run the New Relic Kubernetes integration. +tolerations: + - operator: "Exists" + effect: "NoSchedule" + - operator: "Exists" + effect: "NoExecute" + +updateStrategy: RollingUpdate + +# Sends data to staging, can be set as a global. +# global.nrStaging +nrStaging: false + +daemonSet: + # Annotations to add to the DaemonSet. + annotations: {} + +# Annotations to add to the resulting Pods of the DaemonSet. +podAnnotations: {} + +# If host network should be enabled for fluentbit pods. +# There are some inputs like UDP which will require this setting to be true as they need to bind to the host network. +hostNetwork: + +# When low data mode is enabled only minimal attributes are added to the logs. Kubernetes labels and +# annotations are not included. The plugin.type, plugin.version and plugin.source attributes are minified +# into the plugin.source attribute. +# Can be set as a global: global.lowDataMode +# lowDataMode: false + +extraVolumes: [] +# - name: systemdlog +# hostPath: +# path: /run/log/journal + +extraVolumeMounts: [] +# - name: systemdlog +# mountPath: /run/log/journal + +initContainers: +# - name: init +# image: busybox +# command: ["sh", "-c", 'echo "hello world"'] + +windows: + initContainers: +# - name: init +# image: ... +# command: [...] + +# -- Sets pod dnsConfig. Can also be configured with `global.dnsConfig` +dnsConfig: {} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/Chart.yaml new file mode 100644 index 000000000..99b75c589 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +appVersion: 2.2.1 +description: A Helm chart for the New Relic Pixie integration. +home: https://hub.docker.com/u/newrelic +icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg +keywords: +- newrelic +- pixie +- monitoring +maintainers: +- name: nserrino +- name: philkuz +- name: htroisi +- name: vuqtran88 +name: newrelic-pixie +sources: +- https://github.com/newrelic/ +version: 2.1.6 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/README.md new file mode 100644 index 000000000..228a3676d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/README.md @@ -0,0 +1,166 @@ +# newrelic-pixie + +## Chart Details + +This chart will deploy the New Relic Pixie Integration. + +IMPORTANT: In order to retrieve the Pixie cluster id from the `pl-cluster-secrets` the integration needs to be deployed in the same namespace as Pixie. By default, Pixie is installed in the `pl` namespace. Alternatively the `clusterId` can be configured manually when installing the chart. In this case the integration can be deployed to any namespace. + +## Configuration + +| Parameter | Description | Default | +| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| `global.cluster` - `cluster` | The cluster name for the Kubernetes cluster. Required. | | +| `global.licenseKey` - `licenseKey` | The New Relic license key (stored in a secret). Required. | | +| `global.lowDataMode` - `lowDataMode` | If `true`, the integration performs heavier sampling on the Pixie span data and sets the collect interval to 15 seconds instead of 10 seconds. | false | +| `global.nrStaging` - `nrStaging` | Send data to staging (requires a staging license key). | false | +| `apiKey` | The Pixie API key (stored in a secret). Required. | | +| `clusterId` | The Pixie cluster id. Optional. Read from the `pl-cluster-secrets` secret if empty. | | +| `endpoint` | The Pixie endpoint. Required when using Pixie Open Source. | | +| `verbose` | Whether the integration should run in verbose mode or not. | false | +| `global.customSecretName` - `customSecretName` | Name of an existing Secret object, not created by this chart, where the New Relic license is stored | | +| `global.customSecretLicenseKey` - `customSecretLicenseKey` | Key in the existing Secret object, indicated by `customSecretName`, where the New Relic license key is stored. | | +| `image.pullSecrets` | Image pull secrets. | `nil` | +| `customSecretApiKeyName` | Name of an existing Secret object, not created by this chart, where the Pixie API key is stored. | | +| `customSecretApiKeyKey` | Key in the existing Secret object, indicated by `customSecretApiKeyName`, where the Pixie API key is stored. | | +| `podLabels` | Labels added to each Job pod | `{}` | +| `podAnnotations` | Annotations added to each Job pod | `{}` | +| `job.annotations` | Annotations added to the `newrelic-pixie` Job resource | `{}` | +| `job.labels` | Annotations added to the `newrelic-pixie` Job resource | `{}` | +| `nodeSelector` | Node label to use for scheduling. | `{}` | +| `tolerations` | List of node taints to tolerate (requires Kubernetes >= 1.6). | `[]` | +| `affinity` | Node affinity to use for scheduling. | `{}` | +| `proxy` | Set proxy to connect to Pixie Cloud and New Relic. | | +| `customScripts` | YAML containing custom scripts for long-term data retention. The results of the custom scripts will be stored in New Relic. See [custom scripts](#custom-scripts) for YAML format. | `{}` | +| `customScriptsConfigMap` | Name of an existing ConfigMap object containing custom script for long-term data retention. This configuration takes precedence over `customScripts`. | | +| `excludeNamespacesRegex` | Observability data for namespaces matching this RE2 regex is not sent to New Relic. If empty, observability data for all namespaces is sent to New Relic. | | +| `excludePodsRegex` | Observability data for pods (across all namespaces) matching this RE2 regex is not sent to New Relic. If empty, observability data for all pods (in non-excluded namespaces) is sent to New Relic. | | + +## Example + +Make sure you have [added the New Relic chart repository.](../../README.md#installing-charts) + +Then, to install this chart, run the following command: + +```sh +helm install newrelic/newrelic-pixie \ + --set cluster= \ + --set licenseKey= \ + --set apiKey= \ + --namespace pl \ + --generate-name +``` + +## Globals + +**Important:** global parameters have higher precedence than locals with the same name. + +These are meant to be used when you are writing a chart with subcharts. It helps to avoid +setting values multiple times on different subcharts. + +More information on globals and subcharts can be found at [Helm's official documentation](https://helm.sh/docs/topics/chart_template_guide/subcharts_and_globals/). + +| Parameter | +| ------------------------------- | +| `global.cluster` | +| `global.licenseKey` | +| `global.customSecretName` | +| `global.customSecretLicenseKey` | +| `global.lowDataMode` | +| `global.nrStaging` | + +## Custom scripts + +Custom scripts can either be configured directly in `customScripts` or be provided through an existing ConfigMap `customScriptsConfigMap`. + +The entries in the ConfigMap should contain file-like keys with the `.yaml` extension. Each file in the ConfigMap should be valid YAML and contain the following keys: + + * name (string): the name of the script + * description (string): description of the script + * frequencyS (int): frequency to execute the script in seconds + * scripts (string): the actual PXL script to execute + * addExcludes (optional boolean, `false` by default): add pod and namespace excludes to the custom script + +For more detailed information about the custom scripts see [the New Relic Pixie integration repo](https://github.com/newrelic/newrelic-pixie-integration/). + +```yaml +customScripts: + custom1.yaml: | + name: "custom1" + description: "Custom script 1" + frequencyS: 60 + script: | + import px + + df = px.DataFrame(table='http_events', start_time=px.plugin.start_time) + + ns_prefix = df.ctx['namespace'] + '/' + df.container = df.ctx['container_name'] + df.pod = px.strip_prefix(ns_prefix, df.ctx['pod']) + df.service = px.strip_prefix(ns_prefix, df.ctx['service']) + df.namespace = df.ctx['namespace'] + + df.status_code = df.resp_status + + df = df.groupby(['status_code', 'pod', 'container','service', 'namespace']).agg( + latency_min=('latency', px.min), + latency_max=('latency', px.max), + latency_sum=('latency', px.sum), + latency_count=('latency', px.count), + time_=('time_', px.max), + ) + + df.latency_min = df.latency_min / 1000000 + df.latency_max = df.latency_max / 1000000 + df.latency_sum = df.latency_sum / 1000000 + + df.cluster_name = px.vizier_name() + df.cluster_id = px.vizier_id() + df.pixie = 'pixie' + + px.export( + df, px.otel.Data( + resource={ + 'service.name': df.service, + 'k8s.container.name': df.container, + 'service.instance.id': df.pod, + 'k8s.pod.name': df.pod, + 'k8s.namespace.name': df.namespace, + 'px.cluster.id': df.cluster_id, + 'k8s.cluster.name': df.cluster_name, + 'instrumentation.provider': df.pixie, + }, + data=[ + px.otel.metric.Summary( + name='http.server.duration', + description='measures the duration of the inbound HTTP request', + # Unit is not supported yet + # unit='ms', + count=df.latency_count, + sum=df.latency_sum, + quantile_values={ + 0.0: df.latency_min, + 1.0: df.latency_max, + }, + attributes={ + 'http.status_code': df.status_code, + }, + )], + ), + ) +``` + + +## Resources + +The default set of resources assigned to the pods is shown below: + +```yaml +resources: + limits: + memory: 250M + requests: + cpu: 100m + memory: 250M +``` + diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/ci/test-values.yaml new file mode 100644 index 000000000..580f9b0ba --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/ci/test-values.yaml @@ -0,0 +1,5 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + apiKey: 1234567890abcdef + cluster: test-cluster +clusterId: foobar diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/NOTES.txt new file mode 100644 index 000000000..d54283889 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/NOTES.txt @@ -0,0 +1,27 @@ +{{- if (include "newrelic-pixie.areValuesValid" .) }} + +Your deployment of the New Relic Pixie integration is complete. + +Please ensure this integration is deployed in the same namespace +as Pixie or manually specify the clusterId. +{{- else -}} +############################################################### +#### ERROR: You did not set all the required values. #### +############################################################### + +This deployment will be incomplete until you set all the required values: + +* Cluster name +* New Relic license key +* Pixie API key + +For a simple installation to be fixed, run: + + helm upgrade {{ .Release.Name }} \ + --set cluster=YOUR-CLUSTER-NAME \ + --set licenseKey=YOUR-LICENSE-KEY \ + --set apiKey=YOUR-API-KEY \ + -n {{ .Release.Namespace }} \ + newrelic/newrelic-pixie + +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/_helpers.tpl new file mode 100644 index 000000000..40b9c68df --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/_helpers.tpl @@ -0,0 +1,172 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "newrelic-pixie.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "newrelic-pixie.namespace" -}} +{{- if .Values.namespace -}} + {{- .Values.namespace -}} +{{- else -}} + {{- .Release.Namespace | default "pl" -}} +{{- 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). +*/}} +{{- define "newrelic-pixie.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if ne $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* Generate basic labels */}} +{{- define "newrelic-pixie.labels" }} +app: {{ template "newrelic-pixie.name" . }} +app.kubernetes.io/name: {{ include "newrelic-pixie.name" . }} +chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} +heritage: {{.Release.Service }} +release: {{.Release.Name }} +{{- end }} + +{{- define "newrelic-pixie.cluster" -}} +{{- if .Values.global -}} + {{- if .Values.global.cluster -}} + {{- .Values.global.cluster -}} + {{- else -}} + {{- .Values.cluster | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.cluster | default "" -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-pixie.nrStaging" -}} +{{- if .Values.global }} + {{- if .Values.global.nrStaging }} + {{- .Values.global.nrStaging -}} + {{- end -}} +{{- else if .Values.nrStaging }} + {{- .Values.nrStaging -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-pixie.licenseKey" -}} +{{- if .Values.global}} + {{- if .Values.global.licenseKey }} + {{- .Values.global.licenseKey -}} + {{- else -}} + {{- .Values.licenseKey | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.licenseKey | default "" -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-pixie.apiKey" -}} +{{- if .Values.global}} + {{- if .Values.global.apiKey }} + {{- .Values.global.apiKey -}} + {{- else -}} + {{- .Values.apiKey | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.apiKey | default "" -}} +{{- end -}} +{{- end -}} + +{{- /* +adapted from https://github.com/newrelic/helm-charts/blob/af747af93fb5b912374196adc59b552965b6e133/library/common-library/templates/_low-data-mode.tpl +TODO: actually use common-library chart dep +*/ -}} +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic-pixie.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the customSecretName where the New Relic license is being stored. +*/}} +{{- define "newrelic-pixie.customSecretName" -}} +{{- if .Values.global }} + {{- if .Values.global.customSecretName }} + {{- .Values.global.customSecretName -}} + {{- else -}} + {{- .Values.customSecretName | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.customSecretName | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the customSecretApiKeyName where the Pixie API key is being stored. +*/}} +{{- define "newrelic-pixie.customSecretApiKeyName" -}} + {{- .Values.customSecretApiKeyName | default "" -}} +{{- end -}} + +{{/* +Return the customSecretLicenseKey +*/}} +{{- define "newrelic-pixie.customSecretLicenseKey" -}} +{{- if .Values.global }} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- else -}} + {{- .Values.customSecretLicenseKey | default "" -}} + {{- end -}} +{{- else -}} + {{- .Values.customSecretLicenseKey | default "" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the customSecretApiKeyKey +*/}} +{{- define "newrelic-pixie.customSecretApiKeyKey" -}} + {{- .Values.customSecretApiKeyKey | default "" -}} +{{- end -}} + +{{/* +Returns if the template should render, it checks if the required values +licenseKey and cluster are set. +*/}} +{{- define "newrelic-pixie.areValuesValid" -}} +{{- $cluster := include "newrelic-pixie.cluster" . -}} +{{- $licenseKey := include "newrelic-pixie.licenseKey" . -}} +{{- $apiKey := include "newrelic-pixie.apiKey" . -}} +{{- $customSecretName := include "newrelic-pixie.customSecretName" . -}} +{{- $customSecretLicenseKey := include "newrelic-pixie.customSecretLicenseKey" . -}} +{{- $customSecretApiKeyKey := include "newrelic-pixie.customSecretApiKeyKey" . -}} +{{- and (or (and $licenseKey $apiKey) (and $customSecretName $customSecretLicenseKey $customSecretApiKeyKey)) $cluster}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/configmap.yaml new file mode 100644 index 000000000..19f7fe61a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/configmap.yaml @@ -0,0 +1,12 @@ +{{- if (include "newrelic-pixie.areValuesValid" .) }} +{{- if .Values.customScripts }} +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: {{ template "newrelic-pixie.namespace" . }} + labels: {{ include "newrelic-pixie.labels" . | indent 4 }} + name: {{ template "newrelic-pixie.fullname" . }}-scripts +data: +{{- toYaml .Values.customScripts | nindent 2 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/job.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/job.yaml new file mode 100644 index 000000000..25c9ba5ed --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/job.yaml @@ -0,0 +1,165 @@ +{{- if (include "newrelic-pixie.areValuesValid" .) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "newrelic-pixie.fullname" . }} + namespace: {{ template "newrelic-pixie.namespace" . }} + labels: + {{- include "newrelic-pixie.labels" . | trim | nindent 4}} + {{- if ((.Values.job).labels) }} + {{- toYaml .Values.job.labels | nindent 4 }} + {{- end }} + {{- if ((.Values.job).annotations) }} + annotations: + {{ toYaml .Values.job.annotations | nindent 4 | trim }} + {{- end }} +spec: + backoffLimit: 4 + ttlSecondsAfterFinished: 600 + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "newrelic-pixie.name" . }} + release: {{.Release.Name }} + {{- if .Values.podLabels }} + {{- toYaml .Values.podLabels | nindent 8 }} + {{- end }} + {{- if .Values.podAnnotations }} + annotations: + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + spec: + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml .Values.image.pullSecrets | nindent 8 }} + {{- end }} + restartPolicy: Never + initContainers: + - name: cluster-registration-wait + image: "{{ (.Values.clusterRegistrationWaitImage).repository | default "gcr.io/pixie-oss/pixie-dev-public/curl" }}:{{ (.Values.clusterRegistrationWaitImage).tag | default "1.0" }}" + imagePullPolicy: "{{ (.Values.clusterRegistrationWaitImage).pullPolicy | default "IfNotPresent" }}" + command: ['sh', '-c', 'set -x; + URL="https://${SERVICE_NAME}:${SERVICE_PORT}/readyz"; + until [ $(curl -m 0.5 -s -o /dev/null -w "%{http_code}" -k ${URL}) -eq 200 ]; do + echo "Waiting for cluster registration. If this takes too long check the vizier-cloud-connector logs." + sleep 2; + done; + '] + env: + # The name of the Pixie service which connects to Pixie Cloud for cluster registration. + - name: SERVICE_NAME + value: "vizier-cloud-connector-svc" + - name: SERVICE_PORT + value: "50800" + containers: + - name: {{ template "newrelic-pixie.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + env: + - name: CLUSTER_NAME + value: {{ template "newrelic-pixie.cluster" . }} + - name: NR_LICENSE_KEY + valueFrom: + secretKeyRef: + {{- if (include "newrelic-pixie.licenseKey" .) }} + name: {{ template "newrelic-pixie.fullname" . }}-secrets + key: newrelicLicenseKey + {{- else }} + name: {{ include "newrelic-pixie.customSecretName" . }} + key: {{ include "newrelic-pixie.customSecretLicenseKey" . }} + {{- end }} + - name: PIXIE_API_KEY + valueFrom: + secretKeyRef: + {{- if (include "newrelic-pixie.apiKey" .) }} + name: {{ template "newrelic-pixie.fullname" . }}-secrets + key: pixieApiKey + {{- else }} + name: {{ include "newrelic-pixie.customSecretApiKeyName" . }} + key: {{ include "newrelic-pixie.customSecretApiKeyKey" . }} + {{- end }} + - name: PIXIE_CLUSTER_ID + {{- if .Values.clusterId }} + value: {{ .Values.clusterId -}} + {{- else }} + valueFrom: + secretKeyRef: + key: cluster-id + name: pl-cluster-secrets + {{- end }} + {{- if .Values.verbose }} + - name: VERBOSE + value: "true" + {{- end }} + {{- if (include "newrelic-pixie.lowDataMode" .) }} + - name: COLLECT_INTERVAL_SEC + value: "15" + - name: HTTP_SPAN_LIMIT + value: "750" + - name: DB_SPAN_LIMIT + value: "250" + {{- else }} + - name: COLLECT_INTERVAL_SEC + value: "10" + - name: HTTP_SPAN_LIMIT + value: "1500" + - name: DB_SPAN_LIMIT + value: "500" + {{- end }} + {{- if (include "newrelic-pixie.nrStaging" .) }} + - name: NR_OTLP_HOST + value: "staging-otlp.nr-data.net:4317" + {{- end }} + {{- if or .Values.endpoint (include "newrelic-pixie.nrStaging" .) }} + - name: PIXIE_ENDPOINT + {{- if .Values.endpoint }} + value: {{ .Values.endpoint | quote }} + {{- else }} + value: "staging.withpixie.dev:443" + {{- end }} + {{- end }} + {{- if .Values.proxy }} + - name: HTTP_PROXY + value: {{ .Values.proxy | quote }} + - name: HTTPS_PROXY + value: {{ .Values.proxy | quote }} + {{- end }} + {{- if .Values.excludePodsRegex }} + - name: EXCLUDE_PODS_REGEX + value: {{ .Values.excludePodsRegex | quote }} + {{- end }} + {{- if .Values.excludeNamespacesRegex }} + - name: EXCLUDE_NAMESPACES_REGEX + value: {{ .Values.excludeNamespacesRegex | quote }} + {{- end }} + {{- if .Values.resources }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + {{- end }} + {{- if or .Values.customScriptsConfigMap .Values.customScripts }} + volumeMounts: + - name: scripts + mountPath: "/scripts" + readOnly: true + volumes: + - name: scripts + configMap: + {{- if .Values.customScriptsConfigMap }} + name: {{ .Values.customScriptsConfigMap }} + {{- else }} + name: {{ template "newrelic-pixie.fullname" . }}-scripts + {{- end}} + {{- end }} + {{- if $.Values.nodeSelector }} + nodeSelector: + {{- toYaml $.Values.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: + {{- toYaml .Values.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.affinity }} + affinity: + {{- toYaml .Values.affinity | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/secret.yaml new file mode 100644 index 000000000..4d9561877 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/templates/secret.yaml @@ -0,0 +1,20 @@ +{{- if (include "newrelic-pixie.areValuesValid" .) }} +{{- $licenseKey := include "newrelic-pixie.licenseKey" . -}} +{{- $apiKey := include "newrelic-pixie.apiKey" . -}} +{{- if or $apiKey $licenseKey}} +apiVersion: v1 +kind: Secret +metadata: + namespace: {{ template "newrelic-pixie.namespace" . }} + labels: {{ include "newrelic-pixie.labels" . | indent 4 }} + name: {{ template "newrelic-pixie.fullname" . }}-secrets +type: Opaque +data: + {{- if $licenseKey }} + newrelicLicenseKey: {{ $licenseKey | b64enc }} + {{- end }} + {{- if $apiKey }} + pixieApiKey: {{ include "newrelic-pixie.apiKey" . | b64enc -}} + {{- end }} +{{- end }} +{{- end}} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/configmap.yaml new file mode 100644 index 000000000..ecba6363b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/configmap.yaml @@ -0,0 +1,44 @@ +suite: test custom scripts ConfigMap +templates: + - templates/configmap.yaml +tests: + - it: ConfigMap is created + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + customScripts: + custom1.yaml: | + name: "custom1" + description: "Custom script 1" + frequencyS: 60 + script: | + import px + df = px.DataFrame(table='http_events', start_time=px.plugin.start_time) + asserts: + - isKind: + of: ConfigMap + - equal: + path: data.custom1\.yaml + value: |- + name: "custom1" + description: "Custom script 1" + frequencyS: 60 + script: | + import px + df = px.DataFrame(table='http_events', start_time=px.plugin.start_time) + - equal: + path: metadata.name + value: RELEASE-NAME-newrelic-pixie-scripts + - equal: + path: metadata.namespace + value: NAMESPACE + - it: ConfigMap is empty + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + customScripts: {} + asserts: + - hasDocuments: + count: 0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/jobs.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/jobs.yaml new file mode 100644 index 000000000..03a3d86b8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/tests/jobs.yaml @@ -0,0 +1,138 @@ +suite: test job +templates: + - templates/job.yaml +tests: + - it: Test primary fields of job + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + image: + tag: "latest" + asserts: + - isKind: + of: Job + - equal: + path: "metadata.name" + value: "RELEASE-NAME-newrelic-pixie" + - equal: + path: "metadata.namespace" + value: "NAMESPACE" + - equal: + path: "spec.template.spec.containers[0].image" + value: "newrelic/newrelic-pixie-integration:latest" + - equal: + path: "spec.template.spec.containers[0].env" + value: + - name: CLUSTER_NAME + value: test-cluster + - name: NR_LICENSE_KEY + valueFrom: + secretKeyRef: + key: newrelicLicenseKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_API_KEY + valueFrom: + secretKeyRef: + key: pixieApiKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_CLUSTER_ID + valueFrom: + secretKeyRef: + key: cluster-id + name: pl-cluster-secrets + - isEmpty: + path: "spec.template.spec.containers[0].volumeMounts" + - isEmpty: + path: "spec.template.spec.volumes" + - it: Job with clusterId + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + clusterId: "cid123" + asserts: + - equal: + path: "spec.template.spec.containers[0].env" + value: + - name: CLUSTER_NAME + value: test-cluster + - name: NR_LICENSE_KEY + valueFrom: + secretKeyRef: + key: newrelicLicenseKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_API_KEY + valueFrom: + secretKeyRef: + key: pixieApiKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_CLUSTER_ID + value: "cid123" + - it: Job with Pixie endpoint + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + clusterId: "cid123" + endpoint: "withpixie.ai:443" + asserts: + - equal: + path: "spec.template.spec.containers[0].env" + value: + - name: CLUSTER_NAME + value: test-cluster + - name: NR_LICENSE_KEY + valueFrom: + secretKeyRef: + key: newrelicLicenseKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_API_KEY + valueFrom: + secretKeyRef: + key: pixieApiKey + name: RELEASE-NAME-newrelic-pixie-secrets + - name: PIXIE_CLUSTER_ID + value: "cid123" + - name: PIXIE_ENDPOINT + value: "withpixie.ai:443" + - it: Job with custom scripts + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + customScripts: + custom1.yaml: | + name: "custom1" + asserts: + - equal: + path: "spec.template.spec.containers[0].volumeMounts" + value: + - name: scripts + mountPath: "/scripts" + readOnly: true + - equal: + path: "spec.template.spec.volumes[0]" + value: + name: scripts + configMap: + name: RELEASE-NAME-newrelic-pixie-scripts + - it: Job with custom script in defined ConfigMap + set: + cluster: "test-cluster" + licenseKey: "license123" + apiKey: "api123" + customScriptsConfigMap: "myconfigmap" + asserts: + - equal: + path: "spec.template.spec.containers[0].volumeMounts" + value: + - name: scripts + mountPath: "/scripts" + readOnly: true + - equal: + path: "spec.template.spec.volumes[0]" + value: + name: scripts + configMap: + name: myconfigmap diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/values.yaml new file mode 100644 index 000000000..0be8992ac --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-pixie/values.yaml @@ -0,0 +1,75 @@ +# IMPORTANT: The Kubernetes cluster name +# https://docs.newrelic.com/docs/kubernetes-monitoring-integration +# cluster: "" + +# The New Relic license key +# licenseKey: "" + +# The Pixie API key +# apiKey: "" + +# The Pixie Cluster Id +# clusterId: + +# The Pixie endpoint +# endpoint: + +# If you already have a secret where the New Relic license key is stored, indicate its name here +# customSecretName: +# The key in the customSecretName secret that contains the New Relic license key +# customSecretLicenseKey: +# If you already have a secret where the Pixie API key is stored, indicate its name here +# customSecretApiKeyName: +# The key in the customSecretApiKeyName secret that contains the Pixie API key +# customSecretApiKeyKey: + +clusterRegistrationWaitImage: + repository: gcr.io/pixie-oss/pixie-dev-public/curl + tag: "1.0" + pullPolicy: IfNotPresent + +image: + repository: newrelic/newrelic-pixie-integration + tag: "" + pullPolicy: IfNotPresent + pullSecrets: [] + # - name: regsecret + +resources: + limits: + memory: 250M + requests: + cpu: 100m + memory: 250M + +# -- Annotations to add to the pod. +podAnnotations: {} +# -- Additional labels for chart pods +podLabels: {} + +job: + # job.annotations -- Annotations to add to the Job. + annotations: {} + # job.labels -- Labels to add to the Job. + labels: {} + +proxy: {} + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +customScripts: {} +# Optionally the scripts can be provided in an already existing ConfigMap: +# customScriptsConfigMap: + +excludeNamespacesRegex: +excludePodsRegex: + +# When low data mode is enabled the integration performs heavier sampling on the Pixie span data +# and sets the collect interval to 15 seconds instead of 10 seconds. +# Can be set as a global: global.lowDataMode or locally as newrelic-pixie.lowDataMode +# @default -- false +lowDataMode: diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.lock new file mode 100644 index 000000000..1dc01d3a1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T00:20:40.371047222Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.yaml new file mode 100644 index 000000000..0169bd6f3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/Chart.yaml @@ -0,0 +1,22 @@ +annotations: + configuratorVersion: 1.18.0 +apiVersion: v2 +appVersion: v2.37.8 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy Prometheus with New Relic Prometheus Configurator. +keywords: +- newrelic +- prometheus +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: newrelic-prometheus-agent +type: application +version: 1.15.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md new file mode 100644 index 000000000..069b9a79b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md @@ -0,0 +1,244 @@ +# newrelic-prometheus-agent + +A Helm chart to deploy Prometheus with New Relic Prometheus Configurator. + +# Description + +This chart deploys Prometheus Server in Agent mode configured by the `newrelic-prometheus-configurator`. + +The solution is deployed as a StatefulSet for sharding proposes. +Each Pod will execute the `newrelic-prometheus-configurator` init container which will convert the provided config to a config file in the Prometheus format. Once the init container finishes and saves the config in a shared volume, the container running Prometheus in Agent mode will start. + +```mermaid +graph LR + subgraph pod[Pod] + direction TB + subgraph volume[shared volume] + plain[Prometheus Config] + end + + subgraph init-container[init Container] + configurator[Configurator] --> plain[Prometheus Config] + end + + subgraph container[Main Container] + plain[Prometheus Config] --> prom-agent[Prometheus-Agent] + end + + end + + subgraph configMap + NewRelic-Config --> configurator[Configurator] + end + +classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; +classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; +classDef pod fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; +class configurator,init-container,container,prom-agent k8s; +class volume plain; +class pod pod; + +``` + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add newrelic-prometheus https://newrelic.github.io/newrelic-prometheus-configurator +helm upgrade --install newrelic newrelic-prometheus/newrelic-prometheus-agent -f your-custom-values.yaml +``` + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Configuration + +The configuration used is similar to the [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/), but it includes some syntactic sugar to make easy to set up some special use-cases like Kubernetes targets, sharding and some New Relic related settings like remote write endpoints. + +The configurator will create [scrape_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config), [relabel_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config), [remote_write](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) and other entries based on the defined configuration. + +As general rules: +- Configs parameters having the same name as the [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) should have similar behavior. For example, the `tls_config` defined inside a `Kubernetes.jobs` will have the same definition as [tls_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config) of Prometheus and will affect all targets scraped by that job. +- Configs starting with `extra_` prefix will be appended to the ones created by the Configurator. For example, the relabel configs defined in `extra_relabel_config` on the Kubernetes section will be appended to the end of the list that is already being generated by the Configurator for filtering, sharding, metadata decoration, etc. + +### Default Kubernetes jobs configuration + +By default, some Kubernetes objects are discovered and scraped by Prometheus. Taking into account the snippet from `values.yaml` below: + +```yaml + integrations_filter: + enabled: true + source_labels: ["app.kubernetes.io/name", "app.newrelic.io/name", "k8s-app"] + app_values: ["redis", "traefik", "calico", "nginx", "coredns", "etcd", "cockroachdb", "velero", "harbor", "argocd"] + jobs: + - job_name_prefix: default + target_discovery: + pod: true + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + - job_name_prefix: newrelic + integrations_filter: + enabled: false + target_discovery: + pod: true + endpoints: true + filter: + annotations: + newrelic.io/scrape: true +``` + +All pods and endpoints with the `newrelic.io/scrape: true` annotation will be scraped by default. + +Moreover, the solution will scrape as well all pods and endpoints with the `prometheus.io/scrape: true` annotations and +having one of the labels matching the integrations_filter configuration. + +Notice that at any point you can turn off the integrations filters and scrape all pods and services annotated with +`prometheus.io/scrape: true` by setting `config.kubernetes.integrations_filter.integrations_filter: false` or turning +it off in any specific job. + +### Kubernetes job examples + +#### API Server metrics +By default, the API Server Service named `kubernetes` is created in the `default` namespace. The following configuration will scrape metrics from all endpoints behind the mentioned service using the Prometheus Pod bearer token as Authorization Header: + +```yaml +config: + kubernetes: + jobs: + - job_name_prefix: apiserver + target_discovery: + endpoints: true + extra_relabel_config: + # Filter endpoints on `default` namespace associated to `kubernetes` service. + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name] + action: keep + regex: default;kubernetes + + scheme: https + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + insecure_skip_verify: true + authorization: + credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token +``` + +### Metrics Filtering + +Check [docs](https://github.com/newrelic/newrelic-prometheus-configurator/blob/main/docs/MetricsFilters.md) for a detailed explanation and examples of how to filter metrics and labels. + +### Self metrics + +By default, it is defined as a job in `static_target.jobs` to obtain self-metrics. Particularly, a snippet like the one +below is used. If you define your own static_targets jobs, it is important to also include this kind of job in order +to keep getting self-metrics. + +```yaml +config: + static_targets: + jobs: + - job_name: self-metrics + targets: + - "localhost:9090" + extra_metric_relabel_config: + - source_labels: [__name__] + regex: "" + action: keep +``` + +### Low data mode + +There are two mechanisms to reduce the amount of data that this integration sends to New Relic. See this snippet from the `values.yaml` file: +```yaml +lowDataMode: false + +config: + common: + scrape_interval: 30s +``` + +You might set `lowDataMode` flag to `true` (it will filter some metrics which can also be collected using New Relic Kubernetes integration), check +`values.yaml` for details. + +It is also possible to adjust how frequently Prometheus scrapes the targets by setting up the` config.common.scrape_interval` value. + +### Affinities and tolerations + +The New Relic common library allows you to set affinities, tolerations, and node selectors globally using e.g. `.global.affinity` to ease the configuration +when you use this chart using `nri-bundle`. This chart has an extra level of granularity to the components that it deploys: +control plane, ksm, and kubelet. + +Take this snippet as an example: +```yaml +global: + affinity: {} +affinity: {} +``` + +The order to set the affinity is to set `affinity` field (at root level), if that value is empty, the chart fallbacks to `global.affinity`. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets pod/node affinities set almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster`. Note it will be set as an external label in prometheus configuration, it will have precedence over `config.common.external_labels.cluster_name` and `customAttributes.cluster_name``. | +| config | object | See `values.yaml` | It holds the New Relic Prometheus configuration. Here you can easily set up Prometheus to get set metrics, discover ponds and endpoints Kubernetes and send metrics to New Relic using remote-write. | +| config.common | object | See `values.yaml` | Include global configuration for Prometheus agent. | +| config.common.scrape_interval | string | `"30s"` | How frequently to scrape targets by default, unless a different value is specified on the job. | +| config.extra_remote_write | object | `nil` | It includes additional remote-write configuration. Note this configuration is not parsed, so valid [prometheus remote_write configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) should be provided. | +| config.extra_scrape_configs | list | `[]` | It is possible to include extra scrape configuration in [prometheus format](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config). Please note, it should be a valid Prometheus configuration which will not be parsed by the chart. WARNING extra_scrape_configs is a raw Prometheus config. Therefore, the metrics collected thanks to it will not have by default the metadata (pod_name, service_name, ...) added by the configurator for the static or kubernetes jobs. This configuration should be used as a workaround whenever kubernetes and static job do not cover a particular use-case. | +| config.kubernetes | object | See `values.yaml` | It allows defining scrape jobs for Kubernetes in a simple way. | +| config.kubernetes.integrations_filter.app_values | list | `["redis","traefik","calico","nginx","coredns","kube-dns","etcd","cockroachdb","velero","harbor","argocd"]` | app_values used to create the regex used in the relabel config added by the integration filters configuration. Note that a single regex will be created from this list, example: '.*(?i)(app1|app2|app3).*' | +| config.kubernetes.integrations_filter.enabled | bool | `true` | enabling the integration filters, merely the targets having one of the specified labels matching one of the values of app_values are scraped. Each job configuration can override this default. | +| config.kubernetes.integrations_filter.source_labels | list | `["app.kubernetes.io/name","app.newrelic.io/name","k8s-app"]` | source_labels used to fetch label values in the relabel config added by the integration filters configuration | +| config.newrelic_remote_write | object | See `values.yaml` | Newrelic remote-write configuration settings. | +| config.static_targets | object | See `values.yaml`. | It allows defining scrape jobs for targets with static URLs. | +| config.static_targets.jobs | list | See `values.yaml`. | List of static target jobs. By default, it defines a job to get self-metrics. Please note, if you define `static_target.jobs` and would like to keep self-metrics you need to include a job like the one defined by default. | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| customAttributes | object | `{}` | Adds extra attributes to prometheus external labels. Can be configured also with `global.customAttributes`. Please note, values defined in `common.config.externar_labels` will have precedence over `customAttributes`. | +| customSecretLicenseKey | string | `""` | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` | +| customSecretName | string | `""` | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| extraVolumeMounts | list | `[]` | Defines where to mount volumes specified with `extraVolumes` | +| extraVolumes | list | `[]` | Volumes to mount in the containers | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | `false` | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| images.configurator | object | See `values.yaml` | Image for New Relic configurator. | +| images.prometheus | object | See `values.yaml` | Image for prometheus which is executed in agent mode. | +| images.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| lowDataMode | bool | false | Reduces the number of metrics sent in order to reduce costs. It can be configured also with `global.lowDataMode`. Specifically, it makes Prometheus stop reporting some Kubernetes cluster-specific metrics, you can see details in `static/lowdatamodedefaults.yaml`. | +| metric_type_override | object | `{"enabled":true}` | It holds the configuration for metric type override. If enabled, a series of metric relabel configs will be added to `config.newrelic_remote_write.extra_write_relabel_configs`, you can check the whole list in `static/metrictyperelabeldefaults.yaml` | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| nrStaging | bool | `false` | Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` | +| podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. | +| podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` | +| podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| rbac.create | bool | `true` | Whether the chart should automatically create the RBAC objects required to run. | +| rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | +| resources | object | `{}` | Resource limits to be added to all pods created by the integration. | +| serviceAccount | object | See `values.yaml` | Settings controlling ServiceAccount creation. | +| serviceAccount.create | bool | `true` | Whether the chart should automatically create the ServiceAccount objects required to run. | +| sharding | string | See `values.yaml` | Set up Prometheus replicas to allow horizontal scalability. | +| tolerations | list | `[]` | Sets pod's tolerations to node taints almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) | +| verboseLog | bool | `false` | Sets the debug log to Prometheus and prometheus-configurator or all integrations if it is set globally. Can be configured also with `global.verboseLog` | + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md.gotmpl new file mode 100644 index 000000000..8738b7329 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/README.md.gotmpl @@ -0,0 +1,209 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +# Description + +This chart deploys Prometheus Server in Agent mode configured by the `newrelic-prometheus-configurator`. + +The solution is deployed as a StatefulSet for sharding proposes. +Each Pod will execute the `newrelic-prometheus-configurator` init container which will convert the provided config to a config file in the Prometheus format. Once the init container finishes and saves the config in a shared volume, the container running Prometheus in Agent mode will start. + +```mermaid +graph LR + subgraph pod[Pod] + direction TB + subgraph volume[shared volume] + plain[Prometheus Config] + end + + subgraph init-container[init Container] + configurator[Configurator] --> plain[Prometheus Config] + end + + subgraph container[Main Container] + plain[Prometheus Config] --> prom-agent[Prometheus-Agent] + end + + end + + subgraph configMap + NewRelic-Config --> configurator[Configurator] + end + +classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; +classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; +classDef pod fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; +class configurator,init-container,container,prom-agent k8s; +class volume plain; +class pod pod; + +``` + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add newrelic-prometheus https://newrelic.github.io/newrelic-prometheus-configurator +helm upgrade --install newrelic newrelic-prometheus/newrelic-prometheus-agent -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Configuration + +The configuration used is similar to the [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/), but it includes some syntactic sugar to make easy to set up some special use-cases like Kubernetes targets, sharding and some New Relic related settings like remote write endpoints. + +The configurator will create [scrape_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config), [relabel_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config), [remote_write](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) and other entries based on the defined configuration. + +As general rules: +- Configs parameters having the same name as the [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) should have similar behavior. For example, the `tls_config` defined inside a `Kubernetes.jobs` will have the same definition as [tls_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config) of Prometheus and will affect all targets scraped by that job. +- Configs starting with `extra_` prefix will be appended to the ones created by the Configurator. For example, the relabel configs defined in `extra_relabel_config` on the Kubernetes section will be appended to the end of the list that is already being generated by the Configurator for filtering, sharding, metadata decoration, etc. + +### Default Kubernetes jobs configuration + +By default, some Kubernetes objects are discovered and scraped by Prometheus. Taking into account the snippet from `values.yaml` below: + +```yaml + integrations_filter: + enabled: true + source_labels: ["app.kubernetes.io/name", "app.newrelic.io/name", "k8s-app"] + app_values: ["redis", "traefik", "calico", "nginx", "coredns", "etcd", "cockroachdb", "velero", "harbor", "argocd"] + jobs: + - job_name_prefix: default + target_discovery: + pod: true + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + - job_name_prefix: newrelic + integrations_filter: + enabled: false + target_discovery: + pod: true + endpoints: true + filter: + annotations: + newrelic.io/scrape: true +``` + +All pods and endpoints with the `newrelic.io/scrape: true` annotation will be scraped by default. + +Moreover, the solution will scrape as well all pods and endpoints with the `prometheus.io/scrape: true` annotations and +having one of the labels matching the integrations_filter configuration. + +Notice that at any point you can turn off the integrations filters and scrape all pods and services annotated with +`prometheus.io/scrape: true` by setting `config.kubernetes.integrations_filter.integrations_filter: false` or turning +it off in any specific job. + +### Kubernetes job examples + +#### API Server metrics +By default, the API Server Service named `kubernetes` is created in the `default` namespace. The following configuration will scrape metrics from all endpoints behind the mentioned service using the Prometheus Pod bearer token as Authorization Header: + +```yaml +config: + kubernetes: + jobs: + - job_name_prefix: apiserver + target_discovery: + endpoints: true + extra_relabel_config: + # Filter endpoints on `default` namespace associated to `kubernetes` service. + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name] + action: keep + regex: default;kubernetes + + scheme: https + tls_config: + ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + insecure_skip_verify: true + authorization: + credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token +``` + +### Metrics Filtering + +Check [docs](https://github.com/newrelic/newrelic-prometheus-configurator/blob/main/docs/MetricsFilters.md) for a detailed explanation and examples of how to filter metrics and labels. + +### Self metrics + +By default, it is defined as a job in `static_target.jobs` to obtain self-metrics. Particularly, a snippet like the one +below is used. If you define your own static_targets jobs, it is important to also include this kind of job in order +to keep getting self-metrics. + +```yaml +config: + static_targets: + jobs: + - job_name: self-metrics + targets: + - "localhost:9090" + extra_metric_relabel_config: + - source_labels: [__name__] + regex: "" + action: keep +``` + +### Low data mode + +There are two mechanisms to reduce the amount of data that this integration sends to New Relic. See this snippet from the `values.yaml` file: +```yaml +lowDataMode: false + +config: + common: + scrape_interval: 30s +``` + +You might set `lowDataMode` flag to `true` (it will filter some metrics which can also be collected using New Relic Kubernetes integration), check +`values.yaml` for details. + +It is also possible to adjust how frequently Prometheus scrapes the targets by setting up the` config.common.scrape_interval` value. + + +### Affinities and tolerations + +The New Relic common library allows you to set affinities, tolerations, and node selectors globally using e.g. `.global.affinity` to ease the configuration +when you use this chart using `nri-bundle`. This chart has an extra level of granularity to the components that it deploys: +control plane, ksm, and kubelet. + +Take this snippet as an example: +```yaml +global: + affinity: {} +affinity: {} +``` + +The order to set the affinity is to set `affinity` field (at root level), if that value is empty, the chart fallbacks to `global.affinity`. + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/ci/test-values.yaml new file mode 100644 index 000000000..ac5ed6bb0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/ci/test-values.yaml @@ -0,0 +1,6 @@ +licenseKey: fakeLicenseKey +cluster: test-cluster-name +images: + configurator: + repository: ct/prometheus-configurator + tag: ct diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml new file mode 100644 index 000000000..726815755 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/lowdatamodedefaults.yaml @@ -0,0 +1,6 @@ +# This file contains an entry of the array `extra_write_relabel_configs` to filter +# metrics on Low Data Mode. These metrics are already collected by the New Relic Kubernetes Integration. +low_data_mode: +- action: drop + source_labels: [__name__] + regex: "kube_.+|container_.+|machine_.+|cadvisor_.+" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml new file mode 100644 index 000000000..c0a277409 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/static/metrictyperelabeldefaults.yaml @@ -0,0 +1,17 @@ +# This file contains an entry of the array `extra_write_relabel_configs` to override metric types. +# https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-remote-write/set-your-prometheus-remote-write-integration#override-mapping +metrics_type_relabel: +- source_labels: [__name__] + separator: ; + regex: timeseries_write_(.*) # Cockroach + target_label: newrelic_metric_type + replacement: counter + action: replace +- source_labels: [__name__] + separator: ; + regex: sql_byte(.*) # Cockroach + target_label: newrelic_metric_type + replacement: counter + action: replace +# Note that adding more elements to this list could cause a possible breaking change to users already leveraging affected metrics. +# Therefore, before adding new entries check if any users is relying already on those metrics and warn them. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/_helpers.tpl new file mode 100644 index 000000000..6cc58e251 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/_helpers.tpl @@ -0,0 +1,165 @@ +{{- /* Return the newrelic-prometheus configuration */ -}} + +{{- /* it builds the common configuration from configurator config, cluster name and custom attributes */ -}} +{{- define "newrelic-prometheus.configurator.common" -}} +{{- $tmp := dict "external_labels" (dict "cluster_name" (include "newrelic.common.cluster" . )) -}} + +{{- if .Values.config -}} + {{- if .Values.config.common -}} + {{- $_ := mustMerge $tmp .Values.config.common -}} + {{- end -}} +{{- end -}} + +{{- $tmpCustomAttribute := dict "external_labels" (include "newrelic.common.customAttributes" . | fromYaml ) -}} +{{- $tmp = mustMerge $tmp $tmpCustomAttribute -}} + +common: +{{- $tmp | toYaml | nindent 2 -}} + +{{- end -}} + + +{{- /* it builds the newrelic_remote_write configuration from configurator config */ -}} +{{- define "newrelic-prometheus.configurator.newrelic_remote_write" -}} +{{- $tmp := dict -}} + +{{- if include "newrelic.common.nrStaging" . -}} + {{- $_ := set $tmp "staging" true -}} +{{- end -}} + +{{- if include "newrelic.common.fedramp.enabled" . -}} + {{- $_ := set $tmp "fedramp" (dict "enabled" true) -}} +{{- end -}} + +{{- $extra_write_relabel_configs :=(include "newrelic-prometheus.configurator.extra_write_relabel_configs" . | fromYaml) -}} +{{- if ne (len $extra_write_relabel_configs.list) 0 -}} + {{- $_ := set $tmp "extra_write_relabel_configs" $extra_write_relabel_configs.list -}} +{{- end -}} + +{{- if .Values.config -}} +{{- if .Values.config.newrelic_remote_write -}} + {{- $tmp = mustMerge $tmp .Values.config.newrelic_remote_write -}} +{{- end -}} +{{- end -}} + +{{- if not (empty $tmp) -}} + {{- dict "newrelic_remote_write" $tmp | toYaml -}} +{{- end -}} + +{{- end -}} + +{{- /* it builds the extra_write_relabel_configs configuration merging: lowdatamode, user ones, and metrictyperelabeldefaults */ -}} +{{- define "newrelic-prometheus.configurator.extra_write_relabel_configs" -}} + +{{- $extra_write_relabel_configs := list -}} +{{- if (include "newrelic.common.lowDataMode" .) -}} + {{- $lowDataModeRelabelConfig := .Files.Get "static/lowdatamodedefaults.yaml" | fromYaml -}} + {{- $extra_write_relabel_configs = concat $extra_write_relabel_configs $lowDataModeRelabelConfig.low_data_mode -}} +{{- end -}} + +{{- if .Values.metric_type_override -}} + {{- if .Values.metric_type_override.enabled -}} + {{- $metricTypeOverride := .Files.Get "static/metrictyperelabeldefaults.yaml" | fromYaml -}} + {{- $extra_write_relabel_configs = concat $extra_write_relabel_configs $metricTypeOverride.metrics_type_relabel -}} + {{- end -}} +{{- end -}} + +{{- if .Values.config -}} +{{- if .Values.config.newrelic_remote_write -}} + {{- /* it concatenates the defined 'extra_write_relabel_configs' to the ones defined in lowDataMode */ -}} + {{- if .Values.config.newrelic_remote_write.extra_write_relabel_configs -}} + {{- $extra_write_relabel_configs = concat $extra_write_relabel_configs .Values.config.newrelic_remote_write.extra_write_relabel_configs -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- /* sadly in helm we cannot pass back a list without putting it into a tmp dict */ -}} +{{ dict "list" $extra_write_relabel_configs | toYaml}} + +{{- end -}} + + +{{- /* it builds the extra_remote_write configuration from configurator config */ -}} +{{- define "newrelic-prometheus.configurator.extra_remote_write" -}} +{{- if .Values.config -}} + {{- if .Values.config.extra_remote_write -}} +extra_remote_write: + {{- .Values.config.extra_remote_write | toYaml | nindent 2 -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-prometheus.configurator.static_targets" -}} +{{- if .Values.config -}} + {{- if .Values.config.static_targets -}} +static_targets: + {{- .Values.config.static_targets | toYaml | nindent 2 -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-prometheus.configurator.extra_scrape_configs" -}} +{{- if .Values.config -}} + {{- if .Values.config.extra_scrape_configs -}} +extra_scrape_configs: + {{- .Values.config.extra_scrape_configs | toYaml | nindent 2 -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-prometheus.configurator.kubernetes" -}} +{{- if .Values.config -}} +{{- if .Values.config.kubernetes -}} +kubernetes: + {{- if .Values.config.kubernetes.jobs }} + jobs: + {{- .Values.config.kubernetes.jobs | toYaml | nindent 2 -}} + {{- end -}} + + {{- if .Values.config.kubernetes.integrations_filter }} + integrations_filter: + {{- .Values.config.kubernetes.integrations_filter | toYaml | nindent 4 -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "newrelic-prometheus.configurator.sharding" -}} + {{- if .Values.sharding -}} +sharding: + total_shards_count: {{ include "newrelic-prometheus.configurator.replicas" . }} + {{- end -}} +{{- end -}} + +{{- define "newrelic-prometheus.configurator.replicas" -}} + {{- if .Values.sharding -}} +{{- .Values.sharding.total_shards_count | default 1 }} + {{- else -}} +1 + {{- end -}} +{{- end -}} + +{{- /* +Return the proper configurator image name +{{ include "newrelic-prometheus.configurator.images.configurator_image" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic-prometheus.configurator.configurator_image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "context" .context) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic-prometheus.configurator.configurator_image.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + +{{- /* +Return the proper image tag for the configurator image +{{ include "newrelic-prometheus.configurator.configurator_image.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic-prometheus.configurator.configurator_image.tag" -}} + {{- .imageRoot.tag | default .context.Chart.Annotations.configuratorVersion | toString -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrole.yaml new file mode 100644 index 000000000..e9d4208e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrole.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - endpoints + - services + - pods + - services + verbs: + - get + - list + - watch + - nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..44244653f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/configmap.yaml new file mode 100644 index 000000000..b775aca74 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/configmap.yaml @@ -0,0 +1,31 @@ +kind: ConfigMap +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +apiVersion: v1 +data: + config.yaml: |- + # Configuration for newrelic-prometheus-configurator + {{- with (include "newrelic-prometheus.configurator.newrelic_remote_write" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.extra_remote_write" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.static_targets" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.extra_scrape_configs" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.common" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.kubernetes" . ) -}} + {{- . | nindent 4 }} + {{- end -}} + {{- with (include "newrelic-prometheus.configurator.sharding" . ) -}} + {{- . | nindent 4 }} + {{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/secret.yaml @@ -0,0 +1,2 @@ +{{- /* Common library will take care of creating the secret or not. */}} +{{- include "newrelic.common.license.secret" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml new file mode 100644 index 000000000..b1e74523e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if include "newrelic.common.serviceAccount.annotations" . }} + annotations: + {{- include "newrelic.common.serviceAccount.annotations" . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/statefulset.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/statefulset.yaml new file mode 100644 index 000000000..846c41c23 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/templates/statefulset.yaml @@ -0,0 +1,157 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ include "newrelic.common.naming.fullname" . }}-headless + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + replicas: {{ include "newrelic-prometheus.configurator.replicas" . }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.images.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + {{- if include "newrelic.common.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + + initContainers: + - name: configurator + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 12 }} + {{- end }} + image: {{ include "newrelic-prometheus.configurator.configurator_image" ( dict "imageRoot" .Values.images.configurator "context" .) }} + imagePullPolicy: {{ .Values.images.configurator.pullPolicy }} + args: + - --input=/etc/configurator/config.yaml + - --output=/etc/prometheus/config/config.yaml + {{- if include "newrelic.common.verboseLog" . }} + - --verbose=true + {{- end }} + {{- with .Values.resources.configurator }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: configurator-config + mountPath: /etc/configurator/ + - name: prometheus-config + mountPath: /etc/prometheus/config + env: + - name: NR_PROM_DATA_SOURCE_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NR_PROM_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + - name: NR_PROM_CHART_VERSION + value: {{ .Chart.Version }} + + containers: + - name: prometheus + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 12 }} + {{- end }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.prometheus "context" .) }} + imagePullPolicy: {{ .Values.images.prometheus.pullPolicy }} + ports: + - containerPort: 9090 + protocol: TCP + livenessProbe: + httpGet: + path: /-/healthy + port: 9090 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 15 + timeoutSeconds: 10 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + httpGet: + path: /-/ready + port: 9090 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 4 + failureThreshold: 3 + successThreshold: 1 + args: + - --config.file=/etc/prometheus/config/config.yaml + - --enable-feature=agent,expand-external-labels + - --storage.agent.retention.max-time=30m + - --storage.agent.wal-truncate-frequency=30m + - --storage.agent.path=/etc/prometheus/storage + {{- if include "newrelic.common.verboseLog" . }} + - --log.level=debug + {{- end }} + {{- with .Values.resources.prometheus }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: prometheus-config + mountPath: /etc/prometheus/config + - name: prometheus-storage + mountPath: /etc/prometheus/storage + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + + volumes: + - name: configurator-config + configMap: + name: {{ include "newrelic.common.naming.fullname" . }} + - name: prometheus-config + emptyDir: {} + - name: prometheus-storage + emptyDir: {} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configmap_test.yaml new file mode 100644 index 000000000..f2dd0468e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configmap_test.yaml @@ -0,0 +1,572 @@ +suite: test configmap +templates: + - templates/configmap.yaml +tests: + - it: config with defaults + set: + licenseKey: license-key-test + cluster: cluster-test + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: replace + regex: timeseries_write_(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + - action: replace + regex: sql_byte(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + static_targets: + jobs: + - extra_metric_relabel_config: + - action: keep + regex: prometheus_agent_active_series|prometheus_target_interval_length_seconds|prometheus_target_scrape_pool_targets|prometheus_remote_storage_samples_pending|prometheus_remote_storage_samples_in_total|prometheus_remote_storage_samples_retried_total|prometheus_agent_corruptions_total|prometheus_remote_storage_shards|prometheus_sd_kubernetes_events_total|prometheus_agent_checkpoint_creations_failed_total|prometheus_agent_checkpoint_deletions_failed_total|prometheus_remote_storage_samples_dropped_total|prometheus_remote_storage_samples_failed_total|prometheus_sd_kubernetes_http_request_total|prometheus_agent_truncate_duration_seconds_sum|prometheus_build_info|process_resident_memory_bytes|process_virtual_memory_bytes|process_cpu_seconds_total|prometheus_remote_storage_bytes_total + source_labels: + - __name__ + job_name: self-metrics + skip_sharding: true + targets: + - localhost:9090 + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + kubernetes: + jobs: + - job_name_prefix: default + target_discovery: + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + pod: true + - integrations_filter: + enabled: false + job_name_prefix: newrelic + target_discovery: + endpoints: true + filter: + annotations: + newrelic.io/scrape: true + pod: true + integrations_filter: + app_values: + - redis + - traefik + - calico + - nginx + - coredns + - kube-dns + - etcd + - cockroachdb + - velero + - harbor + - argocd + enabled: true + source_labels: + - app.kubernetes.io/name + - app.newrelic.io/name + - k8s-app + + - it: staging is enabled + set: + licenseKey: license-key-test + cluster: cluster-test + nrStaging: true + metric_type_override: + enabled: false + config: + static_targets: # Set empty to make this test simple + asserts: + - matchRegex: + path: data["config.yaml"] + pattern: "newrelic_remote_write:\n staging: true" # We do not want to test the whole YAML + + - it: fedramp is enabled + set: + licenseKey: license-key-test + cluster: cluster-test + fedramp: + enabled: true + metric_type_override: + enabled: false + config: + static_targets: # Set empty to make this test simple + asserts: + - matchRegex: + path: data["config.yaml"] + pattern: "newrelic_remote_write:\n fedramp:\n enabled: true" # We do not want to test the whole YAML + + - it: config including remote_write most possible sections + set: + licenseKey: license-key-test + cluster: cluster-test + nrStaging: true + config: + newrelic_remote_write: + proxy_url: http://proxy.url + remote_timeout: 30s + tls_config: + insecure_skip_verify: true + queue_config: + retry_on_http_429: false + extra_write_relabel_configs: + - source_labels: + - __name__ + - instance + regex: node_memory_active_bytes;localhost:9100 + action: drop + extra_remote_write: + - url: "https://second.remote.write" + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: replace + regex: timeseries_write_(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + - action: replace + regex: sql_byte(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + proxy_url: http://proxy.url + queue_config: + retry_on_http_429: false + remote_timeout: 30s + staging: true + tls_config: + insecure_skip_verify: true + extra_remote_write: + - url: https://second.remote.write + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: config including remote_write.extra_write_relabel_configs and not metric relabels + set: + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + newrelic_remote_write: + extra_write_relabel_configs: + - source_labels: + - __name__ + - instance + regex: node_memory_active_bytes;localhost:9100 + action: drop + + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: cluster_name is set from global + set: + licenseKey: license-key-test + global: + cluster: "test" + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: test + scrape_interval: 30s + - it: cluster_name local value has precedence over global precedence + set: + licenseKey: license-key-test + global: + cluster: "test" + cluster: "test2" + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: test2 + scrape_interval: 30s + - it: cluster_name is not overwritten from customAttributes + set: + licenseKey: license-key-test + global: + cluster: "test" + cluster: "test2" + customAttributes: + cluster_name: "test3" + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: test2 + scrape_interval: 30s + + - it: cluster_name has precedence over extra labels has precedence over customAttributes + set: + licenseKey: license-key-test + cluster: test + customAttributes: + attribute: "value" + one: error + cluster_name: "different" + metric_type_override: + enabled: false + config: + common: + external_labels: + one: two + cluster_name: "different" + scrape_interval: 15 + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + attribute: value + cluster_name: test + one: two + scrape_interval: 15 + + - it: config including static_targets overwritten with most possible sections + set: + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + static_targets: + jobs: + - job_name: my-custom-target-authorization-full + targets: + - "192.168.3.1:2379" + params: + q: [ "puppies" ] + oe: [ "utf8" ] + scheme: "https" + body_size_limit: 100MiB + sample_limit: 2000 + target_limit: 2000 + label_limit: 2000 + label_name_length_limit: 2000 + label_value_length_limit: 2000 + scrape_interval: 15s + scrape_timeout: 15s + tls_config: + insecure_skip_verify: true + ca_file: /path/to/ca.crt + key_file: /path/to/key.crt + cert_file: /path/to/cert.crt + server_name: server.name + min_version: TLS12 + authorization: + type: Bearer + credentials: "fancy-credentials" + extra_relabel_config: + - source_labels: [ '__name__', 'instance' ] + regex: node_memory_active_bytes;localhost:9100 + action: drop + extra_metric_relabel_config: + - source_labels: [ '__name__', 'instance' ] + regex: node_memory_active_bytes;localhost:9100 + action: drop + extra_scrape_configs: + - job_name: extra-scrape-config + static_configs: + - targets: + - "192.168.3.1:2379" + labels: + label1: value1 + label2: value2 + scrape_interval: 15s + scrape_timeout: 15s + tls_config: + insecure_skip_verify: true + ca_file: /path/to/ca.crt + key_file: /path/to/key.crt + cert_file: /path/to/cert.crt + server_name: server.name + min_version: TLS12 + authorization: + type: Bearer + credentials: "fancy-credentials" + relabel_configs: + - source_labels: [ '__name__', 'instance' ] + regex: node_memory_active_bytes;localhost:9100 + action: drop + metric_relabel_configs: + - source_labels: [ '__name__', 'instance' ] + regex: node_memory_active_bytes;localhost:9100 + action: drop + # Set empty to make this test simple + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + static_targets: + jobs: + - authorization: + credentials: fancy-credentials + type: Bearer + body_size_limit: 100MiB + extra_metric_relabel_config: + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + extra_relabel_config: + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + job_name: my-custom-target-authorization-full + label_limit: 2000 + label_name_length_limit: 2000 + label_value_length_limit: 2000 + params: + oe: + - utf8 + q: + - puppies + sample_limit: 2000 + scheme: https + scrape_interval: 15s + scrape_timeout: 15s + target_limit: 2000 + targets: + - 192.168.3.1:2379 + tls_config: + ca_file: /path/to/ca.crt + cert_file: /path/to/cert.crt + insecure_skip_verify: true + key_file: /path/to/key.crt + min_version: TLS12 + server_name: server.name + extra_scrape_configs: + - authorization: + credentials: fancy-credentials + type: Bearer + job_name: extra-scrape-config + metric_relabel_configs: + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + relabel_configs: + - action: drop + regex: node_memory_active_bytes;localhost:9100 + source_labels: + - __name__ + - instance + scrape_interval: 15s + scrape_timeout: 15s + static_configs: + - labels: + label1: value1 + label2: value2 + targets: + - 192.168.3.1:2379 + tls_config: + ca_file: /path/to/ca.crt + cert_file: /path/to/cert.crt + insecure_skip_verify: true + key_file: /path/to/key.crt + min_version: TLS12 + server_name: server.name + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: kubernetes config section custom values + set: + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + kubernetes: + integrations_filter: + enabled: false + jobs: + - job_name_prefix: pod-job + target_discovery: + pod: true + endpoints: false + filter: + annotations: + custom/scrape-pod: true + - job_name_prefix: endpoints-job + target_discovery: + pod: false + endpoints: true + filter: + annotations: + custom/scrape-endpoints: true + # Set empty to make this test simple + static_targets: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + kubernetes: + jobs: + - job_name_prefix: pod-job + target_discovery: + endpoints: false + filter: + annotations: + custom/scrape-pod: true + pod: true + - job_name_prefix: endpoints-job + target_discovery: + endpoints: true + filter: + annotations: + custom/scrape-endpoints: true + pod: false + integrations_filter: + app_values: + - redis + - traefik + - calico + - nginx + - coredns + - kube-dns + - etcd + - cockroachdb + - velero + - harbor + - argocd + enabled: false + source_labels: + - app.kubernetes.io/name + - app.newrelic.io/name + - k8s-app + + - it: sharding empty not propagated + set: + licenseKey: license-key-test + cluster: cluster-test + sharding: + metric_type_override: + enabled: false + config: + kubernetes: + static_targets: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: sharding config custom values + set: + licenseKey: license-key-test + cluster: cluster-test + sharding: + total_shards_count: 2 + metric_type_override: + enabled: false + config: + kubernetes: + static_targets: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + sharding: + total_shards_count: 2 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml new file mode 100644 index 000000000..0f5da69bf --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/configurator_image_test.yaml @@ -0,0 +1,57 @@ +suite: test image +templates: + - templates/statefulset.yaml + - templates/configmap.yaml +tests: + - it: configurator image is set + set: + licenseKey: license-key-test + cluster: cluster-test + images: + configurator: + tag: "test" + pullPolicy: Never + prometheus: + tag: "test-2" + asserts: + - template: templates/statefulset.yaml + equal: + path: spec.template.spec.initContainers[0].image + value: "newrelic/newrelic-prometheus-configurator:test" + - equal: + path: spec.template.spec.initContainers[0].imagePullPolicy + value: "Never" + template: templates/statefulset.yaml + - template: templates/statefulset.yaml + equal: + path: spec.template.spec.containers[0].image + value: "quay.io/prometheus/prometheus:test-2" + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: "IfNotPresent" + template: templates/statefulset.yaml + + - it: has a linux node selector by default + set: + licenseKey: license-key-test + cluster: my-cluster + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + template: templates/statefulset.yaml + + - it: has a linux node selector and additional selectors + set: + licenseKey: license-key-test + cluster: my-cluster + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue + template: templates/statefulset.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml new file mode 100644 index 000000000..d1813f135 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/integration_filters_test.yaml @@ -0,0 +1,119 @@ +suite: test configmap with IntegrationFilter +templates: + - templates/configmap.yaml +tests: + - it: config with IntegrationFilter true + set: + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + kubernetes: + integrations_filter: + enabled: true + # Set empty to make this test simple + static_targets: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + kubernetes: + jobs: + - job_name_prefix: default + target_discovery: + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + pod: true + - integrations_filter: + enabled: false + job_name_prefix: newrelic + target_discovery: + endpoints: true + filter: + annotations: + newrelic.io/scrape: true + pod: true + integrations_filter: + app_values: + - redis + - traefik + - calico + - nginx + - coredns + - kube-dns + - etcd + - cockroachdb + - velero + - harbor + - argocd + enabled: true + source_labels: + - app.kubernetes.io/name + - app.newrelic.io/name + - k8s-app + + - it: config with IntegrationFilter false + set: + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + kubernetes: + integrations_filter: + enabled: false + # Set empty to make this test simple + static_targets: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + kubernetes: + jobs: + - job_name_prefix: default + target_discovery: + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + pod: true + - integrations_filter: + enabled: false + job_name_prefix: newrelic + target_discovery: + endpoints: true + filter: + annotations: + newrelic.io/scrape: true + pod: true + integrations_filter: + app_values: + - redis + - traefik + - calico + - nginx + - coredns + - kube-dns + - etcd + - cockroachdb + - velero + - harbor + - argocd + enabled: false + source_labels: + - app.kubernetes.io/name + - app.newrelic.io/name + - k8s-app diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml new file mode 100644 index 000000000..ac3953df6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/tests/lowdatamode_configmap_test.yaml @@ -0,0 +1,138 @@ +suite: test configmap with LowDataMode +templates: + - templates/configmap.yaml +tests: + - it: config with lowDataMode true + set: + licenseKey: license-key-test + cluster: cluster-test + lowDataMode: true + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: kube_.+|container_.+|machine_.+|cadvisor_.+ + source_labels: + - __name__ + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: config with lowDataMode and nrStaging true + set: + licenseKey: license-key-test + cluster: cluster-test + lowDataMode: true + nrStaging: true + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: kube_.+|container_.+|machine_.+|cadvisor_.+ + source_labels: + - __name__ + staging: true + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: config with lowDataMode true from global config + set: + global: + lowDataMode: true + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: false + config: + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: kube_.+|container_.+|machine_.+|cadvisor_.+ + source_labels: + - __name__ + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s + + - it: existing relabel configs are appended to low data mode and metric_type_override relabel configs. + set: + lowDataMode: true + licenseKey: license-key-test + cluster: cluster-test + metric_type_override: + enabled: true + config: + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: my_custom_metric_relabel_config + source_labels: + - __name__ + # Set empty to make this test simple + static_targets: + kubernetes: + asserts: + - equal: + path: data["config.yaml"] + value: |- + # Configuration for newrelic-prometheus-configurator + newrelic_remote_write: + extra_write_relabel_configs: + - action: drop + regex: kube_.+|container_.+|machine_.+|cadvisor_.+ + source_labels: + - __name__ + - action: replace + regex: timeseries_write_(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + - action: replace + regex: sql_byte(.*) + replacement: counter + separator: ; + source_labels: + - __name__ + target_label: newrelic_metric_type + - action: drop + regex: my_custom_metric_relabel_config + source_labels: + - __name__ + common: + external_labels: + cluster_name: cluster-test + scrape_interval: 30s diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/values.yaml new file mode 100644 index 000000000..2fb3ed7bc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/newrelic-prometheus-agent/values.yaml @@ -0,0 +1,473 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster`. +# Note it will be set as an external label in prometheus configuration, it will have precedence over `config.common.external_labels.cluster_name` +# and `customAttributes.cluster_name``. +cluster: "" +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" +# -- In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` +customSecretName: "" +# -- In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` +customSecretLicenseKey: "" + +# -- Adds extra attributes to prometheus external labels. Can be configured also with `global.customAttributes`. Please note, values defined +# in `common.config.externar_labels` will have precedence over `customAttributes`. +customAttributes: {} + +# Images used by the chart for prometheus and New Relic configurator. +# @default See `values.yaml` +images: + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + + # -- Image for New Relic configurator. + # @default -- See `values.yaml` + configurator: + registry: "" + repository: newrelic/newrelic-prometheus-configurator + pullPolicy: IfNotPresent + # @default It defaults to `annotation.configuratorVersion` in `Chart.yaml`. + tag: "" + # -- Image for prometheus which is executed in agent mode. + # @default -- See `values.yaml` + prometheus: + registry: "" + repository: quay.io/prometheus/prometheus + pullPolicy: IfNotPresent + # @default It defaults to `appVersion` in `Chart.yaml`. + tag: "" + +# -- Volumes to mount in the containers +extraVolumes: [] +# -- Defines where to mount volumes specified with `extraVolumes` +extraVolumeMounts: [] + +# -- Settings controlling ServiceAccount creation. +# @default -- See `values.yaml` +serviceAccount: + # -- Whether the chart should automatically create the ServiceAccount objects required to run. + create: true + annotations: {} + # If not set and create is true, a name is generated using the full name template + name: "" + +# -- Additional labels for chart objects. Can be configured also with `global.labels` +labels: {} +# -- Annotations to be added to all pods created by the integration. +podAnnotations: {} +# -- Additional labels for chart pods. Can be configured also with `global.podLabels` +podLabels: {} + +# -- Resource limits to be added to all pods created by the integration. +# @default -- `{}` +resources: + prometheus: {} + +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- `false` +hostNetwork: +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: {} +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} + +# Settings controlling RBAC objects creation. +rbac: + # -- Whether the chart should automatically create the RBAC objects required to run. + create: true + # -- Whether the chart should create Pod Security Policy objects. + pspEnabled: false + +# -- Sets pod/node affinities set almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +affinity: {} +# -- Sets pod's node selector almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +nodeSelector: {} +# -- Sets pod's tolerations to node taints almost globally. (See [Affinities and tolerations](README.md#affinities-and-tolerations)) +tolerations: [] + +# -- (bool) Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` +# @default -- `false` +nrStaging: + +# -- (bool) Reduces the number of metrics sent in order to reduce costs. It can be configured also with `global.lowDataMode`. +# Specifically, it makes Prometheus stop reporting some Kubernetes cluster-specific metrics, you can see details in `static/lowdatamodedefaults.yaml`. +# @default -- false +lowDataMode: + +# -- It holds the configuration for metric type override. If enabled, a series of metric relabel configs will be added to +# `config.newrelic_remote_write.extra_write_relabel_configs`, you can check the whole list in `static/metrictyperelabeldefaults.yaml` +metric_type_override: + enabled: true + +# -- Set up Prometheus replicas to allow horizontal scalability. +# @default -- See `values.yaml` +sharding: + # -- Sets the number of Prometheus instances running on sharding mode. + # @default -- `1` + # total_shards_count: + +# -- (bool) Sets the debug log to Prometheus and prometheus-configurator or all integrations if it is set globally. Can be configured also with `global.verboseLog` +# @default -- `false` +verboseLog: + +# -- It holds the New Relic Prometheus configuration. Here you can easily set up Prometheus to get set metrics, discover +# ponds and endpoints Kubernetes and send metrics to New Relic using remote-write. +# @default -- See `values.yaml` +config: + # -- Include global configuration for Prometheus agent. + # @default -- See `values.yaml` + common: + # -- The labels to add to any timeseries that this Prometheus instance scrapes. + # @default -- `{}` + # external_labels: + # label_key_example: foo-bar + # -- How frequently to scrape targets by default, unless a different value is specified on the job. + scrape_interval: 30s + # -- The default timeout when scraping targets. + # @default -- `10s` + # scrape_timeout: + + # -- (object) Newrelic remote-write configuration settings. + # @default -- See `values.yaml` + newrelic_remote_write: + # # -- Includes additional [relabel configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config) + # # for the New Relic remote write. + # # @default -- `[]` + # extra_write_relabel_configs: [] + + # # Enable the extra_write_relabel_configs below for backwards compatibility with legacy POMI labels. + # # This helpful when migrating from POMI to ensure that Prometheus metrics will contain both labels (e.g. cluster_name and clusterName). + # # For more migration info, please visit the [migration guide](https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-prometheus-agent/migration-guide/). + # - source_labels: [namespace] + # action: replace + # target_label: namespaceName + # - source_labels: [node] + # action: replace + # target_label: nodeName + # - source_labels: [pod] + # action: replace + # target_label: podName + # - source_labels: [service] + # action: replace + # target_label: serviceName + # - source_labels: [cluster_name] + # action: replace + # target_label: clusterName + # - source_labels: [job] + # action: replace + # target_label: scrapedTargetKind + # - source_labels: [instance] + # action: replace + # target_label: scrapedTargetInstance + + # -- Set up the proxy used to send metrics to New Relic. + # @default -- `""` + # proxy_url: + + # -- # Timeout for requests to the remote write endpoint. + # @default -- `30s` + # remote_timeout: + + # -- Fine-tune remote-write behavior: . + # queue_config: + # -- Remote Write shard capacity. + # @default -- `2500` + # capacity: + # -- Maximum number of shards. + # @default -- `200` + # max_shards: + # -- Minimum number of shards. + # @default -- `1` + # min_shards: + # -- Maximum number of samples per send. + # @default -- `500` + # max_samples_per_send: + # -- Maximum time a sample will wait in the buffer. + # @default -- `5s` + # batch_send_deadline: + # -- Initial retry delay. Gets doubled for every retry. + # @default -- `30ms` + # min_backoff: + # -- Maximum retry delay. + # @default -- `5s` + # max_backoff: + # -- Retry upon receiving a 429 status code from the remote-write storage. + # @default -- `false` + # retry_on_http_429: + + # -- (object) It includes additional remote-write configuration. Note this configuration is not parsed, so valid + # [prometheus remote_write configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write) + # should be provided. + extra_remote_write: + + # -- It allows defining scrape jobs for Kubernetes in a simple way. + # @default -- See `values.yaml` + kubernetes: + # NewRelic provides a list of Dashboards, alerts and entities for several Services. The integrations_filter configuration + # allows to scrape only the targets having this experience out of the box. + # If integrations_filter is enabled, then the jobs scrape merely the targets having one of the specified labels matching + # one of the values of app_values. + # Under the hood, a relabel_configs with 'action=keep' are generated, consider it in case any custom extra_relabel_config is needed. + integrations_filter: + # -- enabling the integration filters, merely the targets having one of the specified labels matching + # one of the values of app_values are scraped. Each job configuration can override this default. + enabled: true + # -- source_labels used to fetch label values in the relabel config added by the integration filters configuration + source_labels: ["app.kubernetes.io/name", "app.newrelic.io/name", "k8s-app"] + # -- app_values used to create the regex used in the relabel config added by the integration filters configuration. + # Note that a single regex will be created from this list, example: '.*(?i)(app1|app2|app3).*' + app_values: ["redis", "traefik", "calico", "nginx", "coredns", "kube-dns", "etcd", "cockroachdb", "velero", "harbor", "argocd"] + + # Kubernetes jobs define [kubernetes_sd_configs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) + # to discover and scrape Kubernetes objects. Besides, a set of relabel_configs are included in order to include some Kubernetes metadata as + # Labels. For example, address, metrics_path, URL scheme, prometheus_io_parameters, namespace, pod name, service name and labels are taken + # to set the corresponding labels. + # Please note, the relabeling allows configuring the pod/endpoints scrape using the following annotations: + # - `prometheus.io/scheme`: If the metrics endpoint is secured then you will need to set this to `https` + # - `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 for service endpoints or to + # the default 9102 for pods. + # - `prometheus.io/param_`: To include additional parameters in the scrape URL. + jobs: + # 'default' scrapes all targets having 'prometheus.io/scrape: true'. + # Out of the box, since kubernetes.integrations_filter.enabled=true then only targets selected by the integration filters are considered. + - job_name_prefix: default + target_discovery: + pod: true + endpoints: true + filter: + annotations: + prometheus.io/scrape: true + # -- integrations_filter configuration for this specific job. It overrides kubernetes.integrations_filter configuration + # integrations_filter: + + # 'newrelic' scrapes all targets having 'newrelic.io/scrape: true'. + # This is useful to extend the targets scraped by the 'default' job allowlisting services leveraging `newrelic.io/scrape` annotation + - job_name_prefix: newrelic + integrations_filter: + enabled: false + target_discovery: + pod: true + endpoints: true + filter: + annotations: + newrelic.io/scrape: true + + # -- Set up the job name prefix. The final Prometheus `job` name will be composed of + the target discovery kind. ie: `default-pod` + # @default -- `""` + # - job_name_prefix: + + # -- The target discovery field allows customizing how Kubernetes discovery works. + # target_discovery: + + # -- Whether pods should be discovered. + # @default -- `false` + # pod: + + # -- Whether endpoints should be discovered. + # @default -- `false` + # endpoints: + + # -- Defines filtering criteria, it is possible to set labels and/or annotations. All filters will apply (defined + # filters are taken into account as an "AND operation"). + # @default -- `{}` + # filter: + # -- Map of annotations that the targets should have. If only the annotation name is defined, the filter only checks if exists. + # @default -- `{}` + # annotations: + + # -- Map of labels that the targets should have. If only the label name is defined, the filter only checks if exists. + # @default -- `{}` + # labels: + + # -- Advanced configs of the Kubernetes service discovery `kuberentes_sd_config` options, + # check [prometheus documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config) for details. + # Notice that using `filter` is the recommended way to filter targets to avoid adding load to the API Server. + # additional_config: + # kubeconfig_file: "" + # namespaces: {} + # selectors: {} + # attach_metadata: {} + + + # -- The HTTP resource path on which to fetch metrics from targets. + # Use `prometheus.io/path` pod/service annotation to override this or modify it here. + # @default -- `/metrics` + # metrics_path: + + # -- Optional HTTP URL parameters. + # Use `prometheus.io/param_` pod/service annotation to include additional parameters in the scrape url or modify it here. + # @default -- `{}` + # params: + + # -- Configures the protocol scheme used for requests. + # Annotate the service/pod with `prometheus.io/scheme=https` if the secured port is used or modify it here. + # @default -- `http` + # scheme: + + # -- How frequently to scrape targets from this job. + # @default -- defined in `common.scrape_interval` + # scrape_interval: + + # -- Per-scrape timeout when scraping this job. + # @default -- defined in `common.scrape_timeout` + # scrape_timeout: + + # -- Configures the scrape request's TLS settings. + # @default -- `{}` + # tls_config: + # -- CA certificate file path to validate API server certificate with. + # @default -- `""` + # ca_file: + + # -- Certificate and key files path for client cert authentication to the server. + # @default -- `""` + # cert_file: + # key_file: + + # Disable validation of the server certificate. + # @default -- `false` + # insecure_skip_verify: + + # -- Sets the `Authorization` Bearer token header on every scrape request + # @default -- `{}` + # authorization: + # Sets the credentials to the credentials read from the configured file. + # @default -- `""` + # credentials_file: + + # -- Sets the `Authorization` header on every scrape request with the configured username and password. + # @default -- `{}` + # basic_auth: + # username: + # password_file: + + # -- List of relabeling configurations. Used if needed to add any special filter or label manipulation before the scrape takes place. + # @default -- `[]` + # extra_relabel_config: + + # -- List of metric relabel configurations. Used it to filter metrics and labels after scrape. + # @default -- `[]` + # extra_metric_relabel_config: + + + # -- It allows defining scrape jobs for targets with static URLs. + # @default -- See `values.yaml`. + static_targets: + # -- List of static target jobs. By default, it defines a job to get self-metrics. Please note, if you define `static_target.jobs` and would like to keep + # self-metrics you need to include a job like the one defined by default. + # @default -- See `values.yaml`. + jobs: + - job_name: self-metrics + skip_sharding: true # sharding is skipped to obtain self-metrics from all Prometheus servers. + targets: + - "localhost:9090" + extra_metric_relabel_config: + - source_labels: [__name__] + regex: "\ + prometheus_agent_active_series|\ + prometheus_target_interval_length_seconds|\ + prometheus_target_scrape_pool_targets|\ + prometheus_remote_storage_samples_pending|\ + prometheus_remote_storage_samples_in_total|\ + prometheus_remote_storage_samples_retried_total|\ + prometheus_agent_corruptions_total|\ + prometheus_remote_storage_shards|\ + prometheus_sd_kubernetes_events_total|\ + prometheus_agent_checkpoint_creations_failed_total|\ + prometheus_agent_checkpoint_deletions_failed_total|\ + prometheus_remote_storage_samples_dropped_total|\ + prometheus_remote_storage_samples_failed_total|\ + prometheus_sd_kubernetes_http_request_total|\ + prometheus_agent_truncate_duration_seconds_sum|\ + prometheus_build_info|\ + process_resident_memory_bytes|\ + process_virtual_memory_bytes|\ + process_cpu_seconds_total|\ + prometheus_remote_storage_bytes_total" + action: keep + + # -- The job name assigned to scraped metrics by default. + # @default -- `""`. + # - job_name: + # -- List of target URLs to be scraped by this job. + # @default -- `[]`. + # targets: + + # -- Labels assigned to all metrics scraped from the targets. + # @default -- `{}`. + # labels: + + # -- The HTTP resource path on which to fetch metrics from targets. + # @default -- `/metrics` + # metrics_path: + + # -- Optional HTTP URL parameters. + # @default -- `{}` + # params: + + # -- Configures the protocol scheme used for requests. + # @default -- `http` + # scheme: + + # -- How frequently to scrape targets from this job. + # @default -- defined in `common.scrape_interval` + # scrape_interval: + + # -- Per-scrape timeout when scraping this job. + # @default -- defined in `common.scrape_timeout` + # scrape_timeout: + + # -- Configures the scrape request's TLS settings. + # @default -- `{}` + # tls_config: + # -- CA certificate file path to validate API server certificate with. + # @default -- `""` + # ca_file: + + # -- Certificate and key files path for client cert authentication to the server. + # @default -- `""` + # cert_file: + # key_file: + + # Disable validation of the server certificate. + # @default -- `false` + # insecure_skip_verify: + + # -- Sets the `Authorization` Bearer token header on every scrape request + # @default -- `{}` + # authorization: + # Sets the credentials to the credentials read from the configured file. + # @default -- `""` + # credentials_file: + + # -- Sets the `Authorization` header on every scrape request with the configured username and password. + # @default -- `{}` + # basic_auth: + # username: + # password_file: + + # -- List of relabeling configurations. Used if needed to add any special filter or label manipulation before the scrape takes place. + # @default -- `[]` + # extra_relabel_config: + + # -- List of metric relabel configurations. Used it to filter metrics and labels after scrape. + # @default -- `[]` + # extra_metric_relabel_config: + + + # -- It is possible to include extra scrape configuration in [prometheus format](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config). + # Please note, it should be a valid Prometheus configuration which will not be parsed by the chart. + # WARNING extra_scrape_configs is a raw Prometheus config. Therefore, the metrics collected thanks to it will not have by default the metadata (pod_name, service_name, ...) added by the configurator for the static or kubernetes jobs. + # This configuration should be used as a workaround whenever kubernetes and static job do not cover a particular use-case. + # @default -- `[]` + extra_scrape_configs: [] diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.lock new file mode 100644 index 000000000..d524c9292 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T23:46:01.668441447Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.yaml new file mode 100644 index 000000000..b663237fc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +appVersion: 2.11.0 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic Kube Events router +home: https://docs.newrelic.com/docs/integrations/kubernetes-integration/kubernetes-events/install-kubernetes-events-integration +icon: https://newrelic.com/themes/custom/curio/assets/mediakit/NR_logo_Horizontal.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: nri-kube-events +sources: +- https://github.com/newrelic/nri-kube-events/ +- https://github.com/newrelic/nri-kube-events/tree/main/charts/nri-kube-events +- https://github.com/newrelic/infrastructure-agent/ +version: 3.11.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md new file mode 100644 index 000000000..e42205d97 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md @@ -0,0 +1,79 @@ +# nri-kube-events + +![Version: 3.11.0](https://img.shields.io/badge/Version-3.11.0-informational?style=flat-square) ![AppVersion: 2.11.0](https://img.shields.io/badge/AppVersion-2.11.0-informational?style=flat-square) + +A Helm chart to deploy the New Relic Kube Events router + +**Homepage:** + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-kube-events https://newrelic.github.io/nri-kube-events +helm upgrade --install nri-kube-events/nri-kube-events -f your-custom-values.yaml +``` + +## Source Code + +* +* +* + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` | +| agentHTTPTimeout | string | `"30s"` | Amount of time to wait until timeout to send metrics to the metric forwarder | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Mandatory. Can be configured also with `global.cluster` | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| customAttributes | object | `{}` | Adds extra attributes to the cluster and all the metrics emitted to the backend. Can be configured also with `global.customAttributes` | +| customSecretLicenseKey | string | `""` | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` | +| customSecretName | string | `""` | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` | +| deployment.annotations | object | `{}` | Annotations to add to the Deployment. | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| fedramp.enabled | bool | `false` | Enables FedRAMP. Can be configured also with `global.fedramp.enabled` | +| forwarder | object | `{"resources":{}}` | Resources for the forwarder sidecar container | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | `false` | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| images | object | See `values.yaml` | Images used by the chart for the integration and agents | +| images.agent | object | See `values.yaml` | Image for the New Relic Infrastructure Agent sidecar | +| images.integration | object | See `values.yaml` | Image for the New Relic Kubernetes integration | +| images.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| labels | object | `{}` | Additional labels for chart objects | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` | +| nrStaging | bool | `false` | Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` | +| podAnnotations | object | `{}` | Annotations to add to the pod. | +| podLabels | object | `{}` | Additional labels for chart pods | +| podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| proxy | string | `""` | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` | +| rbac.create | bool | `true` | Specifies whether RBAC resources should be created | +| resources | object | `{}` | Resources for the integration container | +| scrapers | object | See `values.yaml` | Configure the various kinds of scrapers that should be run. | +| serviceAccount | object | See `values.yaml` | Settings controlling ServiceAccount creation | +| serviceAccount.create | bool | `true` | Specifies whether a ServiceAccount should be created | +| sinks | object | See `values.yaml` | Configure where will the metrics be written. Mostly for debugging purposes. | +| sinks.newRelicInfra | bool | `true` | The newRelicInfra sink sends all events to New Relic. | +| sinks.stdout | bool | `false` | Enable the stdout sink to also see all events in the logs. | +| tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` | +| verboseLog | bool | `false` | Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` | + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md.gotmpl new file mode 100644 index 000000000..e77eb7f14 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/README.md.gotmpl @@ -0,0 +1,43 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-kube-events https://newrelic.github.io/nri-kube-events +helm upgrade --install nri-kube-events/nri-kube-events -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-bare-minimum-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-bare-minimum-values.yaml new file mode 100644 index 000000000..3fb7df050 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-bare-minimum-values.yaml @@ -0,0 +1,3 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml new file mode 100644 index 000000000..9fec33dc6 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-map.yaml @@ -0,0 +1,12 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +customAttributes: + test_tag_label: test_tag_value + +image: + kubeEvents: + repository: e2e/nri-kube-events + tag: test + pullPolicy: IfNotPresent diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml new file mode 100644 index 000000000..e12cba339 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-custom-attributes-as-string.yaml @@ -0,0 +1,11 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +customAttributes: '{"test_tag_label": "test_tag_value"}' + +image: + kubeEvents: + repository: e2e/nri-kube-events + tag: test + pullPolicy: IfNotPresent diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-values.yaml new file mode 100644 index 000000000..4e517d666 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/ci/test-values.yaml @@ -0,0 +1,60 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +sinks: + # Enable the stdout sink to also see all events in the logs. + stdout: true + # The newRelicInfra sink sends all events to New relic. + newRelicInfra: true + +customAttributes: + test_tag_label: test_tag_value + +config: + accountID: 111 + region: EU + +rbac: + create: true + +serviceAccount: + create: true + +podAnnotations: + annotation1: "annotation" + +nodeSelector: + kubernetes.io/os: linux + +tolerations: + - key: "key1" + effect: "NoSchedule" + operator: "Exists" + +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + +hostNetwork: true + +dnsConfig: + nameservers: + - 1.2.3.4 + searches: + - my.dns.search.suffix + options: + - name: ndots + value: "1" + +image: + kubeEvents: + repository: e2e/nri-kube-events + tag: test + pullPolicy: IfNotPresent diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/NOTES.txt new file mode 100644 index 000000000..3fd06b4a2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/NOTES.txt @@ -0,0 +1,3 @@ +{{ include "nri-kube-events.compatibility.message.securityContext.runAsUser" . }} + +{{ include "nri-kube-events.compatibility.message.images" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers.tpl new file mode 100644 index 000000000..5d0b8d257 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "nri-kube-events.securityContext.pod" -}} +{{- $defaults := fromYaml ( include "nriKubernetes.securityContext.podDefaults" . ) -}} +{{- $compatibilityLayer := include "nri-kube-events.compatibility.securityContext.pod" . | fromYaml -}} +{{- $commonLibrary := fromYaml ( include "newrelic.common.securityContext.pod" . ) -}} + +{{- $finalSecurityContext := dict -}} +{{- if $commonLibrary -}} + {{- $finalSecurityContext = mustMergeOverwrite $commonLibrary $compatibilityLayer -}} +{{- else -}} + {{- $finalSecurityContext = mustMergeOverwrite $defaults $compatibilityLayer -}} +{{- end -}} +{{- toYaml $finalSecurityContext -}} +{{- end -}} + + + +{{- /* These are the defaults that are used for all the containers in this chart */ -}} +{{- define "nriKubernetes.securityContext.podDefaults" -}} +runAsUser: 1000 +runAsNonRoot: true +{{- end -}} + + + +{{- define "nri-kube-events.securityContext.container" -}} +{{- if include "newrelic.common.securityContext.container" . -}} +{{- include "newrelic.common.securityContext.container" . -}} +{{- else -}} +privileged: false +allowPrivilegeEscalation: false +readOnlyRootFilesystem: true +{{- end -}} +{{- end -}} + + + +{{- /* */ -}} +{{- define "nri-kube-events.agentConfig" -}} +is_forward_only: true +http_server_enabled: true +http_server_port: 8001 +{{ include "newrelic.common.agentConfig.defaults" . }} +{{- end -}} \ No newline at end of file diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers_compatibility.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers_compatibility.tpl new file mode 100644 index 000000000..059cfff12 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/_helpers_compatibility.tpl @@ -0,0 +1,262 @@ +{{/* +Returns a dictionary with legacy runAsUser config. +We know that it only has "one line" but it is separated from the rest of the helpers because it is a temporary things +that we should EOL. The EOL time of this will be marked when we GA the deprecation of Helm v2. +*/}} +{{- define "nri-kube-events.compatibility.securityContext.pod" -}} +{{- if .Values.runAsUser -}} +runAsUser: {{ .Values.runAsUser }} +{{- end -}} +{{- end -}} + + + +{{- /* +Functions to get values from the globals instead of the common library +We make this because there could be difficult to see what is going under +the hood if we use the common-library here. So it is easy to read something +like: +{{- $registry := $oldRegistry | default $newRegistry | default $globalRegistry -}} +*/ -}} +{{- define "nri-kube-events.compatibility.global.registry" -}} + {{- if .Values.global -}} + {{- if .Values.global.images -}} + {{- if .Values.global.images.registry -}} + {{- .Values.global.images.registry -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Functions to fetch integration image configuration from the old .Values.image */ -}} +{{- /* integration's old registry */ -}} +{{- define "nri-kube-events.compatibility.old.integration.registry" -}} + {{- if .Values.image -}} + {{- if .Values.image.kubeEvents -}} + {{- if .Values.image.kubeEvents.registry -}} + {{- .Values.image.kubeEvents.registry -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* integration's old repository */ -}} +{{- define "nri-kube-events.compatibility.old.integration.repository" -}} + {{- if .Values.image -}} + {{- if .Values.image.kubeEvents -}} + {{- if .Values.image.kubeEvents.repository -}} + {{- .Values.image.kubeEvents.repository -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* integration's old tag */ -}} +{{- define "nri-kube-events.compatibility.old.integration.tag" -}} + {{- if .Values.image -}} + {{- if .Values.image.kubeEvents -}} + {{- if .Values.image.kubeEvents.tag -}} + {{- .Values.image.kubeEvents.tag -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* integration's old imagePullPolicy */ -}} +{{- define "nri-kube-events.compatibility.old.integration.pullPolicy" -}} + {{- if .Values.image -}} + {{- if .Values.image.kubeEvents -}} + {{- if .Values.image.kubeEvents.pullPolicy -}} + {{- .Values.image.kubeEvents.pullPolicy -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Functions to fetch agent image configuration from the old .Values.image */ -}} +{{- /* agent's old registry */ -}} +{{- define "nri-kube-events.compatibility.old.agent.registry" -}} + {{- if .Values.image -}} + {{- if .Values.image.infraAgent -}} + {{- if .Values.image.infraAgent.registry -}} + {{- .Values.image.infraAgent.registry -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* agent's old repository */ -}} +{{- define "nri-kube-events.compatibility.old.agent.repository" -}} + {{- if .Values.image -}} + {{- if .Values.image.infraAgent -}} + {{- if .Values.image.infraAgent.repository -}} + {{- .Values.image.infraAgent.repository -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* agent's old tag */ -}} +{{- define "nri-kube-events.compatibility.old.agent.tag" -}} + {{- if .Values.image -}} + {{- if .Values.image.infraAgent -}} + {{- if .Values.image.infraAgent.tag -}} + {{- .Values.image.infraAgent.tag -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* agent's old imagePullPolicy */ -}} +{{- define "nri-kube-events.compatibility.old.agent.pullPolicy" -}} + {{- if .Values.image -}} + {{- if .Values.image.infraAgent -}} + {{- if .Values.image.infraAgent.pullPolicy -}} + {{- .Values.image.infraAgent.pullPolicy -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{/* +Creates the image string needed to pull the integration image respecting the breaking change we made in the values file +*/}} +{{- define "nri-kube-events.compatibility.images.integration" -}} +{{- $globalRegistry := include "nri-kube-events.compatibility.global.registry" . -}} +{{- $oldRegistry := include "nri-kube-events.compatibility.old.integration.registry" . -}} +{{- $newRegistry := .Values.images.integration.registry -}} +{{- $registry := $oldRegistry | default $newRegistry | default $globalRegistry -}} + +{{- $oldRepository := include "nri-kube-events.compatibility.old.integration.repository" . -}} +{{- $newRepository := .Values.images.integration.repository -}} +{{- $repository := $oldRepository | default $newRepository }} + +{{- $oldTag := include "nri-kube-events.compatibility.old.integration.tag" . -}} +{{- $newTag := .Values.images.integration.tag -}} +{{- $tag := $oldTag | default $newTag | default .Chart.AppVersion -}} + +{{- if $registry -}} + {{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} + {{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end -}} + + + +{{/* +Creates the image string needed to pull the agent's image respecting the breaking change we made in the values file +*/}} +{{- define "nri-kube-events.compatibility.images.agent" -}} +{{- $globalRegistry := include "nri-kube-events.compatibility.global.registry" . -}} +{{- $oldRegistry := include "nri-kube-events.compatibility.old.agent.registry" . -}} +{{- $newRegistry := .Values.images.agent.registry -}} +{{- $registry := $oldRegistry | default $newRegistry | default $globalRegistry -}} + +{{- $oldRepository := include "nri-kube-events.compatibility.old.agent.repository" . -}} +{{- $newRepository := .Values.images.agent.repository -}} +{{- $repository := $oldRepository | default $newRepository }} + +{{- $oldTag := include "nri-kube-events.compatibility.old.agent.tag" . -}} +{{- $newTag := .Values.images.agent.tag -}} +{{- $tag := $oldTag | default $newTag -}} + +{{- if $registry -}} + {{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} + {{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the pull policy for the integration image taking into account that we made a breaking change on the values path. +*/}} +{{- define "nri-kube-events.compatibility.images.pullPolicy.integration" -}} +{{- $old := include "nri-kube-events.compatibility.old.integration.pullPolicy" . -}} +{{- $new := .Values.images.integration.pullPolicy -}} + +{{- $old | default $new -}} +{{- end -}} + + + +{{/* +Returns the pull policy for the agent image taking into account that we made a breaking change on the values path. +*/}} +{{- define "nri-kube-events.compatibility.images.pullPolicy.agent" -}} +{{- $old := include "nri-kube-events.compatibility.old.agent.pullPolicy" . -}} +{{- $new := .Values.images.agent.pullPolicy -}} + +{{- $old | default $new -}} +{{- end -}} + + + +{{/* +Returns a merged list of pull secrets ready to be used +*/}} +{{- define "nri-kube-events.compatibility.images.renderPullSecrets" -}} +{{- $list := list -}} + +{{- if .Values.image -}} + {{- if .Values.image.pullSecrets -}} + {{- $list = append $list .Values.image.pullSecrets }} + {{- end -}} +{{- end -}} + +{{- if .Values.images.pullSecrets -}} + {{- $list = append $list .Values.images.pullSecrets -}} +{{- end -}} + +{{- include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" $list "context" .) }} +{{- end -}} + + + +{{- /* Messege to show to the user saying that image value is not supported anymore */ -}} +{{- define "nri-kube-events.compatibility.message.images" -}} +{{- $oldIntegrationRegistry := include "nri-kube-events.compatibility.old.integration.registry" . -}} +{{- $oldIntegrationRepository := include "nri-kube-events.compatibility.old.integration.repository" . -}} +{{- $oldIntegrationTag := include "nri-kube-events.compatibility.old.integration.tag" . -}} +{{- $oldIntegrationPullPolicy := include "nri-kube-events.compatibility.old.integration.pullPolicy" . -}} +{{- $oldAgentRegistry := include "nri-kube-events.compatibility.old.agent.registry" . -}} +{{- $oldAgentRepository := include "nri-kube-events.compatibility.old.agent.repository" . -}} +{{- $oldAgentTag := include "nri-kube-events.compatibility.old.agent.tag" . -}} +{{- $oldAgentPullPolicy := include "nri-kube-events.compatibility.old.agent.pullPolicy" . -}} + +{{- if or $oldIntegrationRegistry $oldIntegrationRepository $oldIntegrationTag $oldIntegrationPullPolicy $oldAgentRegistry $oldAgentRepository $oldAgentTag $oldAgentPullPolicy }} +Configuring image repository an tag under 'image' is no longer supported. +This is the list values that we no longer support: + - image.kubeEvents.registry + - image.kubeEvents.repository + - image.kubeEvents.tag + - image.kubeEvents.pullPolicy + - image.infraAgent.registry + - image.infraAgent.repository + - image.infraAgent.tag + - image.infraAgent.pullPolicy + +Please set: + - images.agent.* to configure the infrastructure-agent forwarder. + - images.integration.* to configure the image in charge of scraping k8s data. + +------ +{{- end }} +{{- end -}} + + + +{{- /* Messege to show to the user saying that image value is not supported anymore */ -}} +{{- define "nri-kube-events.compatibility.message.securityContext.runAsUser" -}} +{{- if .Values.runAsUser }} +WARNING: `runAsUser` is deprecated +================================== + +We have automatically translated your `runAsUser` setting to the new format, but this shimming will be removed in the +future. Please migrate your configs to the new format in the `securityContext` key. +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/agent-configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/agent-configmap.yaml new file mode 100644 index 000000000..02bf8306b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/agent-configmap.yaml @@ -0,0 +1,12 @@ +{{- if .Values.sinks.newRelicInfra -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }}-agent-config + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: | + {{- include "nri-kube-events.agentConfig" . | nindent 4 }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrole.yaml new file mode 100644 index 000000000..cbfd5d9ce --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrole.yaml @@ -0,0 +1,42 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }} +rules: +- apiGroups: + - "" + resources: + - events + - namespaces + - nodes + - jobs + - persistentvolumes + - persistentvolumeclaims + - pods + - services + verbs: + - get + - watch + - list +- apiGroups: + - apps + resources: + - daemonsets + - deployments + verbs: + - get + - watch + - list +- apiGroups: + - batch + resources: + - cronjobs + - jobs + verbs: + - get + - watch + - list +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..fc5dfb8da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/configmap.yaml new file mode 100644 index 000000000..9e4e35f6b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/configmap.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.naming.fullname" . }}-config + namespace: {{ .Release.Namespace }} +data: + config.yaml: |- + sinks: + {{- if .Values.sinks.stdout }} + - name: stdout + {{- end }} + {{- if .Values.sinks.newRelicInfra }} + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: {{ include "newrelic.common.cluster" . }} + agentHTTPTimeout: {{ .Values.agentHTTPTimeout }} + {{- end }} + captureDescribe: {{ .Values.scrapers.descriptions.enabled }} + describeRefresh: {{ .Values.scrapers.descriptions.resyncPeriod | default "24h" }} + captureEvents: {{ .Values.scrapers.events.enabled }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/deployment.yaml new file mode 100644 index 000000000..7ba9eaea9 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/deployment.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + annotations: + {{- if .Values.deployment.annotations }} + {{- toYaml .Values.deployment.annotations | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ include "newrelic.common.naming.name" . }} + template: + metadata: + {{- if .Values.podAnnotations }} + annotations: + {{- toYaml .Values.podAnnotations | nindent 8}} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + {{- with include "nri-kube-events.compatibility.images.renderPullSecrets" . }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + {{- with include "nri-kube-events.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: kube-events + image: {{ include "nri-kube-events.compatibility.images.integration" . }} + imagePullPolicy: {{ include "nri-kube-events.compatibility.images.pullPolicy.integration" . }} + {{- with include "nri-kube-events.securityContext.container" . }} + securityContext: + {{- . | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + args: ["-config", "/app/config/config.yaml", "-loglevel", "debug"] + volumeMounts: + - name: config-volume + mountPath: /app/config + {{- if .Values.sinks.newRelicInfra }} + - name: forwarder + image: {{ include "nri-kube-events.compatibility.images.agent" . }} + imagePullPolicy: {{ include "nri-kube-events.compatibility.images.pullPolicy.agent" . }} + {{- with include "nri-kube-events.securityContext.container" . }} + securityContext: + {{- . | nindent 12 }} + {{- end }} + ports: + - containerPort: {{ get (fromYaml (include "nri-kube-events.agentConfig" .)) "http_server_port" }} + env: + - name: NRIA_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + + - name: NRIA_OVERRIDE_HOSTNAME_SHORT + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + + volumeMounts: + - mountPath: /var/db/newrelic-infra/data + name: tmpfs-data + - mountPath: /var/db/newrelic-infra/user_data + name: tmpfs-user-data + - mountPath: /tmp + name: tmpfs-tmp + - name: config + mountPath: /etc/newrelic-infra.yml + subPath: newrelic-infra.yml + {{- if ((.Values.forwarder).resources) }} + resources: + {{- toYaml .Values.forwarder.resources | nindent 12 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + volumes: + {{- if .Values.sinks.newRelicInfra }} + - name: config + configMap: + name: {{ include "newrelic.common.naming.fullname" . }}-agent-config + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + {{- end }} + - name: config-volume + configMap: + name: {{ include "newrelic.common.naming.fullname" . }}-config + - name: tmpfs-data + emptyDir: {} + - name: tmpfs-user-data + emptyDir: {} + - name: tmpfs-tmp + emptyDir: {} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/secret.yaml @@ -0,0 +1,2 @@ +{{- /* Common library will take care of creating the secret or not. */}} +{{- include "newrelic.common.license.secret" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/serviceaccount.yaml new file mode 100644 index 000000000..07e818da0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/templates/serviceaccount.yaml @@ -0,0 +1,11 @@ +{{- if include "newrelic.common.serviceAccount.create" . }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} + annotations: +{{ include "newrelic.common.serviceAccount.annotations" . | indent 4 }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/agent_configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/agent_configmap_test.yaml new file mode 100644 index 000000000..831b0c5aa --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/agent_configmap_test.yaml @@ -0,0 +1,46 @@ +suite: test configmap for newrelic infra agent +templates: + - templates/agent-configmap.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: has the correct default values + set: + cluster: test-cluster + licenseKey: us-whatever + asserts: + - equal: + path: data["newrelic-infra.yml"] + value: | + is_forward_only: true + http_server_enabled: true + http_server_port: 8001 + + - it: integrates properly with the common library + set: + cluster: test-cluster + licenseKey: us-whatever + fedramp.enabled: true + verboseLog: true + asserts: + - equal: + path: data["newrelic-infra.yml"] + value: | + is_forward_only: true + http_server_enabled: true + http_server_port: 8001 + + log: + level: trace + fedramp: true + + - it: does not template if the http sink is disabled + set: + cluster: test-cluster + licenseKey: us-whatever + sinks: + newRelicInfra: false + asserts: + - hasDocuments: + count: 0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/configmap_test.yaml new file mode 100644 index 000000000..68ad53a57 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/configmap_test.yaml @@ -0,0 +1,139 @@ +suite: test configmap for sinks +templates: + - templates/configmap.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: has the correct sinks when default values used + set: + licenseKey: us-whatever + cluster: a-cluster + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: a-cluster + agentHTTPTimeout: 30s + captureDescribe: true + describeRefresh: 24h + captureEvents: true + + - it: honors agentHTTPTimeout + set: + licenseKey: us-whatever + cluster: a-cluster + agentHTTPTimeout: 10s + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: a-cluster + agentHTTPTimeout: 10s + captureDescribe: true + describeRefresh: 24h + captureEvents: true + + - it: has the correct sinks defined in local values + set: + licenseKey: us-whatever + cluster: a-cluster + sinks: + stdout: true + newRelicInfra: false + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: stdout + captureDescribe: true + describeRefresh: 24h + captureEvents: true + + - it: allows enabling/disabling event scraping + set: + licenseKey: us-whatever + cluster: a-cluster + scrapers: + events: + enabled: false + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: a-cluster + agentHTTPTimeout: 30s + captureDescribe: true + describeRefresh: 24h + captureEvents: false + + - it: allows enabling/disabling description scraping + set: + licenseKey: us-whatever + cluster: a-cluster + scrapers: + descriptions: + enabled: false + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: a-cluster + agentHTTPTimeout: 30s + captureDescribe: false + describeRefresh: 24h + captureEvents: true + + - it: allows changing description resync intervals + set: + licenseKey: us-whatever + cluster: a-cluster + scrapers: + descriptions: + resyncPeriod: 4h + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + - name: newRelicInfra + config: + agentEndpoint: http://localhost:8001/v1/data + clusterName: a-cluster + agentHTTPTimeout: 30s + captureDescribe: true + describeRefresh: 4h + captureEvents: true + + - it: has another document generated with the proper config set + set: + licenseKey: us-whatever + cluster: a-cluster + sinks: + stdout: false + newRelicInfra: false + asserts: + - equal: + path: data["config.yaml"] + value: |- + sinks: + captureDescribe: true + describeRefresh: 24h + captureEvents: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/deployment_test.yaml new file mode 100644 index 000000000..702917bce --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/deployment_test.yaml @@ -0,0 +1,104 @@ +suite: test deployment images +templates: + - templates/deployment.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: deployment image uses pullSecrets + set: + cluster: my-cluster + licenseKey: us-whatever + images: + pullSecrets: + - name: regsecret + asserts: + - equal: + path: spec.template.spec.imagePullSecrets + value: + - name: regsecret + + - it: deployment images use the proper image tag + set: + cluster: test-cluster + licenseKey: us-whatever + images: + integration: + repository: newrelic/nri-kube-events + tag: "latest" + agent: + repository: newrelic/k8s-events-forwarder + tag: "latest" + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: .*newrelic/nri-kube-events:latest$ + - matchRegex: + path: spec.template.spec.containers[1].image + pattern: .*newrelic/k8s-events-forwarder:latest$ + + + - it: by default the agent forwarder templates + set: + cluster: test-cluster + licenseKey: us-whatever + asserts: + - contains: + path: spec.template.spec.containers + any: true + content: + name: forwarder + - contains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: my-release-nri-kube-events-agent-config + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + + - it: agent does not template if the sink is disabled + set: + cluster: test-cluster + licenseKey: us-whatever + sinks: + newRelicInfra: false + asserts: + - notContains: + path: spec.template.spec.containers + any: true + content: + name: forwarder + - notContains: + path: spec.template.spec.volumes + content: + name: config + configMap: + name: my-release-nri-kube-events-agent-config + items: + - key: newrelic-infra.yml + path: newrelic-infra.yml + + - it: has a linux node selector by default + set: + cluster: my-cluster + licenseKey: us-whatever + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + + - it: has a linux node selector and additional selectors + set: + cluster: my-cluster + licenseKey: us-whatever + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/images_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/images_test.yaml new file mode 100644 index 000000000..361be582b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/images_test.yaml @@ -0,0 +1,168 @@ +suite: test image compatibility layer +templates: + - templates/deployment.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: by default the tag is not nil + set: + cluster: test-cluster + licenseKey: us-whatever + asserts: + - notMatchRegex: + path: spec.template.spec.containers[0].image + pattern: ".*nil.*" + - notMatchRegex: + path: spec.template.spec.containers[1].image + pattern: ".*nil.*" + + - it: templates image correctly from the new values + set: + cluster: test-cluster + licenseKey: us-whatever + images: + integration: + registry: ireg + repository: irep + tag: itag + agent: + registry: areg + repository: arep + tag: atag + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: ireg/irep:itag + - equal: + path: spec.template.spec.containers[1].image + value: areg/arep:atag + + - it: templates image correctly from old values + set: + cluster: test-cluster + licenseKey: us-whatever + image: + kubeEvents: + registry: ireg + repository: irep + tag: itag + infraAgent: + registry: areg + repository: arep + tag: atag + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: ireg/irep:itag + - equal: + path: spec.template.spec.containers[1].image + value: areg/arep:atag + + - it: old image values take precedence + set: + cluster: test-cluster + licenseKey: us-whatever + images: + integration: + registry: inew + repository: inew + tag: inew + agent: + registry: anew + repository: anew + tag: anew + image: + kubeEvents: + registry: iold + repository: iold + tag: iold + infraAgent: + registry: aold + repository: aold + tag: aold + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: iold/iold:iold + - equal: + path: spec.template.spec.containers[1].image + value: aold/aold:aold + + - it: pullImagePolicy templates correctly from the new values + set: + cluster: test-cluster + licenseKey: us-whatever + images: + integration: + pullPolicy: new + agent: + pullPolicy: new + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: new + - equal: + path: spec.template.spec.containers[1].imagePullPolicy + value: new + + - it: pullImagePolicy templates correctly from old values + set: + cluster: test-cluster + licenseKey: us-whatever + image: + kubeEvents: + pullPolicy: old + infraAgent: + pullPolicy: old + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: old + - equal: + path: spec.template.spec.containers[1].imagePullPolicy + value: old + + - it: old imagePullPolicy values take precedence + set: + cluster: test-cluster + licenseKey: us-whatever + images: + integration: + pullPolicy: new + agent: + pullPolicy: new + image: + kubeEvents: + pullPolicy: old + infraAgent: + pullPolicy: old + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: old + - equal: + path: spec.template.spec.containers[1].imagePullPolicy + value: old + + - it: imagePullSecrets merge properly + set: + cluster: test-cluster + licenseKey: us-whatever + global: + images: + pullSecrets: + - global: global + images: + pullSecrets: + - images: images + image: + pullSecrets: + - image: image + asserts: + - equal: + path: spec.template.spec.imagePullSecrets + value: + - global: global + - image: image + - images: images diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/security_context_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/security_context_test.yaml new file mode 100644 index 000000000..b2b710331 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/tests/security_context_test.yaml @@ -0,0 +1,77 @@ +suite: test deployment security context +templates: + - templates/deployment.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: pod securityContext set to defaults when no values provided + set: + cluster: my-cluster + licenseKey: us-whatever + asserts: + - equal: + path: spec.template.spec.securityContext + value: + runAsUser: 1000 + runAsNonRoot: true + - it: pod securityContext set common-library values + set: + cluster: test-cluster + licenseKey: us-whatever + podSecurityContext: + foobar: true + asserts: + - equal: + path: spec.template.spec.securityContext.foobar + value: true + - it: pod securityContext compatibility layer overrides values from common-library + set: + cluster: test-cluster + licenseKey: us-whatever + runAsUser: 1001 + podSecurityContext: + runAsUser: 1000 + runAsNonRoot: false + asserts: + - equal: + path: spec.template.spec.securityContext + value: + runAsUser: 1001 + runAsNonRoot: false + - it: pod securityContext compatibility layer overrides defaults + set: + cluster: test-cluster + licenseKey: us-whatever + runAsUser: 1001 + asserts: + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 1001 + - it: set to defaults when no containerSecurityContext set + set: + cluster: my-cluster + licenseKey: us-whatever + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext + value: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + - equal: + path: spec.template.spec.containers[1].securityContext + value: + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + - it: set containerSecurityContext custom values + set: + cluster: test-cluster + licenseKey: us-whatever + containerSecurityContext: + foobar: true + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.foobar + value: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/values.yaml new file mode 100644 index 000000000..fc473991d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-kube-events/values.yaml @@ -0,0 +1,135 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Mandatory. Can be configured also with `global.cluster` +cluster: "" +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" +# -- In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` +customSecretName: "" +# -- In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` +customSecretLicenseKey: "" + +# -- Images used by the chart for the integration and agents +# @default -- See `values.yaml` +images: + # -- Image for the New Relic Kubernetes integration + # @default -- See `values.yaml` + integration: + registry: + repository: newrelic/nri-kube-events + tag: + pullPolicy: IfNotPresent + # -- Image for the New Relic Infrastructure Agent sidecar + # @default -- See `values.yaml` + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.57.2 + pullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + +# -- Resources for the integration container +resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# -- Resources for the forwarder sidecar container +forwarder: + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +rbac: + # -- Specifies whether RBAC resources should be created + create: true + +# -- Settings controlling ServiceAccount creation +# @default -- See `values.yaml` +serviceAccount: + # serviceAccount.create -- (bool) Specifies whether a ServiceAccount should be created + # @default -- `true` + create: + # If not set and create is true, a name is generated using the fullname template + name: "" + # Specify any annotations to add to the ServiceAccount + annotations: + +# -- Annotations to add to the pod. +podAnnotations: {} +deployment: + # deployment.annotations -- Annotations to add to the Deployment. + annotations: {} +# -- Additional labels for chart pods +podLabels: {} +# -- Additional labels for chart objects +labels: {} + +# -- Amount of time to wait until timeout to send metrics to the metric forwarder +agentHTTPTimeout: "30s" + +# -- Configure where will the metrics be written. Mostly for debugging purposes. +# @default -- See `values.yaml` +sinks: + # -- Enable the stdout sink to also see all events in the logs. + stdout: false + # -- The newRelicInfra sink sends all events to New Relic. + newRelicInfra: true + +# -- Configure the various kinds of scrapers that should be run. +# @default -- See `values.yaml` +scrapers: + descriptions: + enabled: true + resyncPeriod: "24h" + events: + enabled: true + +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- `false` +hostNetwork: +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: {} +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +# -- Sets pod/node affinities. Can be configured also with `global.affinity` +affinity: {} +# -- Sets pod's node selector. Can be configured also with `global.nodeSelector` +nodeSelector: {} +# -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` +tolerations: [] + +# -- Adds extra attributes to the cluster and all the metrics emitted to the backend. Can be configured also with `global.customAttributes` +customAttributes: {} + +# -- Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` +proxy: "" + +# -- (bool) Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` +# @default -- `false` +nrStaging: +fedramp: + # -- (bool) Enables FedRAMP. Can be configured also with `global.fedramp.enabled` + # @default -- `false` + enabled: + +# -- (bool) Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` +# @default -- `false` +verboseLog: diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/.helmignore new file mode 100644 index 000000000..f62b5519e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/.helmignore @@ -0,0 +1 @@ +templates/admission-webhooks/job-patch/README.md diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.lock new file mode 100644 index 000000000..d442841bf --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-08-30T00:58:04.140696675Z" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.yaml new file mode 100644 index 000000000..14e37394b --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +appVersion: 1.30.0 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic metadata injection webhook. +home: https://hub.docker.com/r/newrelic/k8s-metadata-injection +icon: https://newrelic.com/assets/newrelic/source/NewRelic-logo-square.svg +keywords: +- infrastructure +- newrelic +- monitoring +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +name: nri-metadata-injection +sources: +- https://github.com/newrelic/k8s-metadata-injection +- https://github.com/newrelic/k8s-metadata-injection/tree/master/charts/nri-metadata-injection +version: 4.22.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md new file mode 100644 index 000000000..dd922ef13 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md @@ -0,0 +1,68 @@ +# nri-metadata-injection + +A Helm chart to deploy the New Relic metadata injection webhook. + +**Homepage:** + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection +helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml +``` + +## Source Code + +* +* + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` | +| certManager.enabled | bool | `false` | Use cert manager for webhook certs | +| certManager.rootCertificateDuration | string | `"43800h"` | Sets the root certificate duration. Defaults to 43800h (5 years). | +| certManager.webhookCertificateDuration | string | `"8760h"` | Sets certificate duration. Defaults to 8760h (1 year). | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| customTLSCertificate | bool | `false` | Use custom tls certificates for the webhook, or let the chart handle it automatically. Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | false | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| image | object | See `values.yaml` | Image for the New Relic Metadata Injector | +| image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| injectOnlyLabeledNamespaces | bool | `false` | Enable the metadata decoration only for pods living in namespaces labeled with 'newrelic-metadata-injection=enabled'. | +| jobImage | object | See `values.yaml` | Image for creating the needed certificates of this webhook to work | +| jobImage.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| jobImage.volumeMounts | list | `[]` | Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies Enforce a read-only root. | +| jobImage.volumes | list | `[]` | Volumes to add to the job container | +| labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` | +| podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. | +| podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` | +| podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| rbac.pspEnabled | bool | `false` | Whether the chart should create Pod Security Policy objects. | +| replicas | int | `1` | | +| resources | object | 100m/30M -/80M | Image for creating the needed certificates of this webhook to work | +| timeoutSeconds | int | `28` | Webhook timeout Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts | +| tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` | + +## Maintainers + +* [juanjjaramillo](https://github.com/juanjjaramillo) +* [csongnr](https://github.com/csongnr) +* [dbudziwojskiNR](https://github.com/dbudziwojskiNR) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md.gotmpl new file mode 100644 index 000000000..752ba8aae --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/README.md.gotmpl @@ -0,0 +1,41 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-metadata-injection https://newrelic.github.io/k8s-metadata-injection +helm upgrade --install nri-metadata-injection/nri-metadata-injection -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/ci/test-values.yaml new file mode 100644 index 000000000..6f79dea93 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/ci/test-values.yaml @@ -0,0 +1,5 @@ +cluster: test-cluster + +image: + repository: e2e/metadata-injection + tag: test # Defaults to AppVersion diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/NOTES.txt b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/NOTES.txt new file mode 100644 index 000000000..544124d11 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/NOTES.txt @@ -0,0 +1,23 @@ +Your deployment of the New Relic metadata injection webhook is complete. You can check on the progress of this by running the following command: + + kubectl get deployments -o wide -w --namespace {{ .Release.Namespace }} {{ template "newrelic.common.naming.fullname" . }} + +{{- if .Values.customTLSCertificate }} +You have configure the chart to use a custom tls certificate, make sure to read the 'Manage custom certificates' section of the official docs to find the instructions on how to finish setting up the webhook. + +https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection +{{- end }} + +To validate the injection of metadata create a dummy pod containing Busybox by running: + + kubectl create -f https://git.io/vPieo + +Check if New Relic environment variables were injected: + + kubectl exec busybox0 -- env | grep NEW_RELIC_METADATA_KUBERNETES + + NEW_RELIC_METADATA_KUBERNETES_CLUSTER_NAME=fsi + NEW_RELIC_METADATA_KUBERNETES_NODE_NAME=nodea + NEW_RELIC_METADATA_KUBERNETES_NAMESPACE_NAME=default + NEW_RELIC_METADATA_KUBERNETES_POD_NAME=busybox0 + NEW_RELIC_METADATA_KUBERNETES_CONTAINER_NAME=busybox diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/_helpers.tpl new file mode 100644 index 000000000..54a23e981 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/_helpers.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} + +{{- /* Allow to change pod defaults dynamically */ -}} +{{- define "nri-metadata-injection.securityContext.pod" -}} +{{- if include "newrelic.common.securityContext.pod" . -}} +{{- include "newrelic.common.securityContext.pod" . -}} +{{- else -}} +fsGroup: 1001 +runAsUser: 1001 +runAsGroup: 1001 +{{- end -}} +{{- end -}} + +{{- /* +Naming helpers +*/ -}} + +{{- define "nri-metadata-injection.name.admission" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.admission" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.admission.serviceAccount" -}} +{{- if include "newrelic.common.serviceAccount.create" . -}} + {{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission") }} +{{- else -}} + {{ include "newrelic.common.serviceAccount.name" . }} +{{- end -}} +{{- end -}} + +{{- define "nri-metadata-injection.name.admission-create" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-create") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.admission-create" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-create") }} +{{- end -}} + +{{- define "nri-metadata-injection.name.admission-patch" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "admission-patch") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.admission-patch" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "admission-patch") }} +{{- end -}} + +{{- define "nri-metadata-injection.name.self-signed-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "self-signed-issuer") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.self-signed-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "self-signed-issuer") }} +{{- end -}} + +{{- define "nri-metadata-injection.name.root-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "root-issuer") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.root-issuer" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "root-issuer") }} +{{- end -}} + +{{- define "nri-metadata-injection.name.webhook-cert" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.name" .) "suffix" "webhook-cert") }} +{{- end -}} + +{{- define "nri-metadata-injection.fullname.webhook-cert" -}} +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" (dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "webhook-cert") }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml new file mode 100644 index 000000000..275b597c8 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrole.yaml @@ -0,0 +1,27 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "nri-metadata-injection.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "newrelic.common.naming.name" $ }}-admission + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - get + - update +{{- if .Values.rbac.pspEnabled }} + - apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ include "nri-metadata-injection.fullname.admission" . }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml new file mode 100644 index 000000000..cf846745e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/clusterrolebinding.yaml @@ -0,0 +1,20 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "nri-metadata-injection.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + app: {{ include "nri-metadata-injection.name.admission" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "nri-metadata-injection.fullname.admission" . }} +subjects: + - kind: ServiceAccount + name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml new file mode 100644 index 000000000..a04f27935 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-createSecret.yaml @@ -0,0 +1,61 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "nri-metadata-injection.fullname.admission-create" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission-create" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "nri-metadata-injection.fullname.admission-create" . }} + {{- if .Values.podAnnotations }} + annotations: + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: + app: {{ include "nri-metadata-injection.name.admission-create" . }} + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 -}} + {{- end }} + containers: + - name: create + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }} + imagePullPolicy: {{ .Values.jobImage.pullPolicy }} + args: + - create + - --host={{ include "newrelic.common.naming.fullname" . }},{{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }} + - --cert-name=tls.crt + - --key-name=tls.key + {{- if .Values.jobImage.volumeMounts }} + volumeMounts: + {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.jobImage.volumes }} + volumes: + {{- .Values.jobImage.volumes | toYaml | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- if .Values.tolerations }} + tolerations: + {{- toYaml .Values.tolerations | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml new file mode 100644 index 000000000..99374ef35 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/job-patchWebhook.yaml @@ -0,0 +1,61 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "nri-metadata-injection.fullname.admission-patch" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission-patch" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + template: + metadata: + name: {{ include "nri-metadata-injection.fullname.admission-patch" . }} + {{- if .Values.podAnnotations }} + annotations: + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: + app: {{ include "nri-metadata-injection.name.admission-patch" . }} + {{- include "newrelic.common.labels" . | nindent 8 }} + spec: + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.jobImage.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 -}} + {{- end }} + containers: + - name: patch + image: {{ include "newrelic.common.images.image" ( dict "defaultRegistry" "registry.k8s.io" "imageRoot" .Values.jobImage "context" .) }} + imagePullPolicy: {{ .Values.jobImage.pullPolicy }} + args: + - patch + - --webhook-name={{ include "newrelic.common.naming.fullname" . }} + - --namespace={{ .Release.Namespace }} + - --secret-name={{ include "nri-metadata-injection.fullname.admission" . }} + - --patch-failure-policy=Ignore + - --patch-validating=false + {{- if .Values.jobImage.volumeMounts }} + volumeMounts: + {{- .Values.jobImage.volumeMounts | toYaml | nindent 10 }} + {{- end }} + {{- if .Values.jobImage.volumes }} + volumes: + {{- .Values.jobImage.volumes | toYaml | nindent 8 }} + {{- end }} + restartPolicy: OnFailure + serviceAccountName: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} + securityContext: + runAsGroup: 2000 + runAsNonRoot: true + runAsUser: 2000 + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- if .Values.tolerations }} + tolerations: + {{- toYaml .Values.tolerations | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml new file mode 100644 index 000000000..899ac95fe --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/psp.yaml @@ -0,0 +1,50 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled) (.Values.rbac.pspEnabled) (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy")) }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ include "nri-metadata-injection.fullname.admission" . }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + privileged: false + # Required to prevent escalations to root. + # allowPrivilegeEscalation: false + # This is redundant with non-root + disallow privilege escalation, + # but we can provide it for defense in depth. + #requiredDropCapabilities: + # - ALL + # Allow core volume types. + volumes: + - 'configMap' + - 'emptyDir' + - 'projected' + - 'secret' + - 'downwardAPI' + - 'persistentVolumeClaim' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Permits the container to run with root privileges as well. + rule: 'RunAsAny' + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml new file mode 100644 index 000000000..e42670257 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/role.yaml @@ -0,0 +1,21 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "nri-metadata-injection.fullname.admission" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml new file mode 100644 index 000000000..e73bf472c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if (and (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "nri-metadata-injection.fullname.admission" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "nri-metadata-injection.fullname.admission" . }} +subjects: + - kind: ServiceAccount + name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml new file mode 100644 index 000000000..027a59089 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/job-patch/serviceaccount.yaml @@ -0,0 +1,14 @@ +{{- $createServiceAccount := include "newrelic.common.serviceAccount.create" . -}} +{{- if (and $createServiceAccount (not .Values.customTLSCertificate) (not .Values.certManager.enabled)) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nri-metadata-injection.fullname.admission.serviceAccount" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade,post-install,post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + labels: + app: {{ include "nri-metadata-injection.name.admission" . }} + {{- include "newrelic.common.labels" . | nindent 4 }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml new file mode 100644 index 000000000..b196d4f59 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/admission-webhooks/mutatingWebhookConfiguration.yaml @@ -0,0 +1,36 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} +{{- if .Values.certManager.enabled }} + annotations: + certmanager.k8s.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} + cert-manager.io/inject-ca-from: {{ printf "%s/%s-root-cert" .Release.Namespace (include "newrelic.common.naming.fullname" .) | quote }} +{{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +webhooks: +- name: metadata-injection.newrelic.com + clientConfig: + service: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + path: "/mutate" +{{- if not .Values.certManager.enabled }} + caBundle: "" +{{- end }} + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] +{{- if .Values.injectOnlyLabeledNamespaces }} + scope: Namespaced + namespaceSelector: + matchLabels: + newrelic-metadata-injection: enabled +{{- end }} + failurePolicy: Ignore + timeoutSeconds: {{ .Values.timeoutSeconds }} + sideEffects: None + admissionReviewVersions: ["v1", "v1beta1"] diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/cert-manager.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/cert-manager.yaml new file mode 100644 index 000000000..502fa44bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/cert-manager.yaml @@ -0,0 +1,53 @@ +{{ if .Values.certManager.enabled }} +--- +# Create a selfsigned Issuer, in order to create a root CA certificate for +# signing webhook serving certificates +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }} + namespace: {{ .Release.Namespace }} +spec: + selfSigned: {} +--- +# Generate a CA Certificate used to sign certificates for the webhook +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "newrelic.common.naming.fullname" . }}-root-cert + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert + duration: {{ .Values.certManager.rootCertificateDuration}} + issuerRef: + name: {{ include "nri-metadata-injection.fullname.self-signed-issuer" . }} + commonName: "ca.webhook.nri" + isCA: true +--- +# Create an Issuer that uses the above generated CA certificate to issue certs +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "nri-metadata-injection.fullname.root-issuer" . }} + namespace: {{ .Release.Namespace }} +spec: + ca: + secretName: {{ include "newrelic.common.naming.fullname" . }}-root-cert +--- + +# Finally, generate a serving certificate for the webhook to use +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "nri-metadata-injection.fullname.webhook-cert" . }} + namespace: {{ .Release.Namespace }} +spec: + secretName: {{ include "nri-metadata-injection.fullname.admission" . }} + duration: {{ .Values.certManager.webhookCertificateDuration }} + issuerRef: + name: {{ include "nri-metadata-injection.fullname.root-issuer" . }} + dnsNames: + - {{ include "newrelic.common.naming.fullname" . }} + - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }} + - {{ include "newrelic.common.naming.fullname" . }}.{{ .Release.Namespace }}.svc +{{ end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/deployment.yaml new file mode 100644 index 000000000..4974dbbc1 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/deployment.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- /* We cannot use the common library here because of a legacy issue */}} + {{- /* `selector` is immutable and the previous chart did not have all the idiomatic labels */}} + app.kubernetes.io/name: {{ include "newrelic.common.naming.name" . }} + template: + metadata: + {{- if .Values.podAnnotations }} + annotations: + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + {{- with include "nri-metadata-injection.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 -}} + {{- end }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} + {{- if include "newrelic.common.hostNetwork" . }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" ( list .Values.image.pullSecrets ) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 -}} + {{- end }} + containers: + - name: {{ include "newrelic.common.naming.name" . }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 10 }} + {{- end }} + env: + - name: clusterName + value: {{ include "newrelic.common.cluster" . }} + ports: + - containerPort: 8443 + protocol: TCP + volumeMounts: + - name: tls-key-cert-pair + mountPath: /etc/tls-key-cert-pair + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 1 + periodSeconds: 1 + {{- if .Values.resources }} + resources: + {{ toYaml .Values.resources | nindent 10 }} + {{- end }} + volumes: + - name: tls-key-cert-pair + secret: + secretName: {{ include "nri-metadata-injection.fullname.admission" . }} + nodeSelector: + kubernetes.io/os: linux + {{ include "newrelic.common.nodeSelector" . | nindent 8 }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 -}} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 -}} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/service.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/service.yaml new file mode 100644 index 000000000..e4a57587c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/templates/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + ports: + - port: 443 + targetPort: 8443 + selector: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 4 }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/cluster_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/cluster_test.yaml new file mode 100644 index 000000000..a28487a06 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/cluster_test.yaml @@ -0,0 +1,39 @@ +suite: test cluster environment variable setup +templates: + - templates/deployment.yaml +release: + name: release + namespace: ns +tests: + - it: clusterName env is properly set + set: + cluster: my-cluster + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: clusterName + value: my-cluster + - it: fail when cluster is not defined + asserts: + - failedTemplate: + errorMessage: There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required. + - it: has a linux node selector by default + set: + cluster: my-cluster + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + - it: has a linux node selector and additional selectors + set: + cluster: my-cluster + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml new file mode 100644 index 000000000..63b6f0534 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/job_serviceaccount_test.yaml @@ -0,0 +1,59 @@ +suite: test job' serviceAccount +templates: + - templates/admission-webhooks/job-patch/job-createSecret.yaml + - templates/admission-webhooks/job-patch/job-patchWebhook.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: my-release-nri-metadata-injection-admission + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + + - it: has a linux node selector by default + set: + cluster: my-cluster + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + + - it: has a linux node selector and additional selectors + set: + cluster: my-cluster + nodeSelector: + aCoolTestLabel: aCoolTestValue + asserts: + - equal: + path: spec.template.spec.nodeSelector + value: + kubernetes.io/os: linux + aCoolTestLabel: aCoolTestValue diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/rbac_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/rbac_test.yaml new file mode 100644 index 000000000..5a69191df --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/rbac_test.yaml @@ -0,0 +1,38 @@ +suite: test RBAC creation +templates: + - templates/admission-webhooks/job-patch/rolebinding.yaml + - templates/admission-webhooks/job-patch/clusterrolebinding.yaml +release: + name: my-release + namespace: my-namespace +tests: + - it: RBAC points to the service account that is created by default + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: true + asserts: + - equal: + path: subjects[0].name + value: my-release-nri-metadata-injection-admission + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: false + serviceAccount.name: sa-test + asserts: + - equal: + path: subjects[0].name + value: sa-test + + - it: RBAC points to the service account the user supplies when serviceAccount is disabled + set: + cluster: test-cluster + rbac.create: true + serviceAccount.create: false + asserts: + - equal: + path: subjects[0].name + value: default diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/volume_mounts_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/volume_mounts_test.yaml new file mode 100644 index 000000000..4a3c1327d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/tests/volume_mounts_test.yaml @@ -0,0 +1,30 @@ +suite: check volume mounts is properly set +templates: + - templates/admission-webhooks/job-patch/job-createSecret.yaml + - templates/admission-webhooks/job-patch/job-patchWebhook.yaml +release: + name: release + namespace: ns +tests: + - it: clusterName env is properly set + set: + cluster: my-cluster + jobImage: + volumeMounts: + - name: test-volume + volumePath: /test-volume + volumes: + - name: test-volume-container + emptyDir: {} + + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: test-volume + volumePath: /test-volume + - contains: + path: spec.template.spec.volumes + content: + name: test-volume-container + emptyDir: {} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/values.yaml new file mode 100644 index 000000000..8ea50aab2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-metadata-injection/values.yaml @@ -0,0 +1,102 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` +cluster: "" + +# -- Image for the New Relic Metadata Injector +# @default -- See `values.yaml` +image: + registry: + repository: newrelic/k8s-metadata-injection + tag: "" # Defaults to chart's appVersion + pullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + +# -- Image for creating the needed certificates of this webhook to work +# @default -- See `values.yaml` +jobImage: + registry: # Defaults to registry.k8s.io + repository: ingress-nginx/kube-webhook-certgen + tag: v1.4.3 + pullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + + # -- Volume mounts to add to the job, you might want to mount tmp if Pod Security Policies + # Enforce a read-only root. + volumeMounts: [] + # - name: tmp + # mountPath: /tmp + + # -- Volumes to add to the job container + volumes: [] + # - name: tmp + # emptyDir: {} + +rbac: + # rbac.pspEnabled -- Whether the chart should create Pod Security Policy objects. + pspEnabled: false + +replicas: 1 + +# -- Additional labels for chart objects. Can be configured also with `global.labels` +labels: {} +# -- Annotations to be added to all pods created by the integration. +podAnnotations: {} +# -- Additional labels for chart pods. Can be configured also with `global.podLabels` +podLabels: {} + +# -- Image for creating the needed certificates of this webhook to work +# @default -- 100m/30M -/80M +resources: + limits: + memory: 80M + requests: + cpu: 100m + memory: 30M + +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- false +hostNetwork: +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: {} +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +certManager: + # certManager.enabled -- Use cert manager for webhook certs + enabled: false + # -- Sets the root certificate duration. Defaults to 43800h (5 years). + rootCertificateDuration: 43800h + # -- Sets certificate duration. Defaults to 8760h (1 year). + webhookCertificateDuration: 8760h + +# -- Sets pod/node affinities. Can be configured also with `global.affinity` +affinity: {} +# -- Sets pod's node selector. Can be configured also with `global.nodeSelector` +nodeSelector: {} +# -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` +tolerations: [] + +# -- Enable the metadata decoration only for pods living in namespaces labeled +# with 'newrelic-metadata-injection=enabled'. +injectOnlyLabeledNamespaces: false + +# -- Use custom tls certificates for the webhook, or let the chart handle it +# automatically. +# Ref: https://docs.newrelic.com/docs/integrations/kubernetes-integration/link-your-applications/link-your-applications-kubernetes#configure-injection +customTLSCertificate: false + +# -- Webhook timeout +# Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts +timeoutSeconds: 28 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/.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/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.lock b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.lock new file mode 100644 index 000000000..934f6dcbc --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +digest: sha256:2e1da613fd8a52706bde45af077779c5d69e9e1641bdf5c982eaf6d1ac67a443 +generated: "2024-09-30T16:12:25.200183-07:00" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.yaml new file mode 100644 index 000000000..d33f07729 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +appVersion: 2.21.4 +dependencies: +- name: common-library + repository: https://helm-charts.newrelic.com + version: 1.3.0 +description: A Helm chart to deploy the New Relic Prometheus OpenMetrics integration +home: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-openmetrics/configure-prometheus-openmetrics-integrations/ +icon: https://newrelic.com/themes/custom/curio/assets/mediakit/new_relic_logo_vertical.svg +keywords: +- prometheus +- newrelic +- monitoring +maintainers: +- name: alvarocabanas + url: https://github.com/alvarocabanas +- name: sigilioso + url: https://github.com/sigilioso +- name: gsanchezgavier + url: https://github.com/gsanchezgavier +- name: kang-makes + url: https://github.com/kang-makes +- name: paologallinaharbur + url: https://github.com/paologallinaharbur +name: nri-prometheus +sources: +- https://github.com/newrelic/nri-prometheus +- https://github.com/newrelic/nri-prometheus/tree/main/charts/nri-prometheus +version: 2.1.19 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md new file mode 100644 index 000000000..0287b2b2a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md @@ -0,0 +1,116 @@ +# nri-prometheus + +A Helm chart to deploy the New Relic Prometheus OpenMetrics integration + +**Homepage:** + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-prometheus https://newrelic.github.io/nri-prometheus +helm upgrade --install newrelic-prometheus nri-prometheus/nri-prometheus -f your-custom-values.yaml +``` + +## Source Code + +* +* + +## Scraping services and endpoints + +When a service is labeled or annotated with `scrape_enabled_label` (defaults to `prometheus.io/scrape`), +`nri-prometheus` will attempt to hit the service directly, rather than the endpoints behind it. + +This is the default behavior for compatibility reasons, but is known to cause issues if more than one endpoint +is behind the service, as metric queries will be load-balanced as well leading to inaccurate histograms. + +In order to change this behaviour set `scrape_endpoints` to `true` and `scrape_services` to `false`. +This will instruct `nri-prometheus` to scrape the underlying endpoints, as Prometheus server does. + +Existing users that are switching to this behavior should note that, depending on the number of endpoints +behind the services in the cluster the load and the metrics reported by those, data ingestion might see +an increase when flipping this option. Resource requirements might also be impacted, again depending on the number of new targets. + +While it is technically possible to set both `scrape_services` and `scrape_endpoints` to true, we do no recommend +doing so as it will lead to redundant metrics being processed, + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Low data mode +See this snippet from the `values.yaml` file: +```yaml +global: + lowDataMode: false +lowDataMode: false +``` + +To reduce the amount ot metrics we send to New Relic, enabling the `lowDataMode` will add [these transformations](static/lowdatamodedefaults.yaml): +```yaml +transformations: + - description: "Low data mode defaults" + ignore_metrics: + # Ignore the following metrics. + # These metrics are already collected by the New Relic Kubernetes Integration. + - prefixes: + - kube_ + - container_ + - machine_ + - cadvisor_ +``` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Sets pod/node affinities. Can be configured also with `global.affinity` | +| cluster | string | `""` | Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` | +| config | object | See `values.yaml` | Provides your own `config.yaml` for this integration. Ref: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-openmetrics/configure-prometheus-openmetrics-integrations/#example-configuration-file | +| containerSecurityContext | object | `{}` | Sets security context (at container level). Can be configured also with `global.containerSecurityContext` | +| customSecretLicenseKey | string | `""` | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` | +| customSecretName | string | `""` | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` | +| dnsConfig | object | `{}` | Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` | +| fedramp.enabled | bool | false | Enables FedRAMP. Can be configured also with `global.fedramp.enabled` | +| fullnameOverride | string | `""` | Override the full name of the release | +| hostNetwork | bool | `false` | Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` | +| image | object | See `values.yaml` | Image for the New Relic Kubernetes integration | +| image.pullSecrets | list | `[]` | The secrets that are needed to pull images from a custom registry. | +| labels | object | `{}` | Additional labels for chart objects. Can be configured also with `global.labels` | +| licenseKey | string | `""` | This set this license key to use. Can be configured also with `global.licenseKey` | +| lowDataMode | bool | false | Reduces number of metrics sent in order to reduce costs. Can be configured also with `global.lowDataMode` | +| nameOverride | string | `""` | Override the name of the chart | +| nodeSelector | object | `{}` | Sets pod's node selector. Can be configured also with `global.nodeSelector` | +| nrStaging | bool | false | Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` | +| podAnnotations | object | `{}` | Annotations to be added to all pods created by the integration. | +| podLabels | object | `{}` | Additional labels for chart pods. Can be configured also with `global.podLabels` | +| podSecurityContext | object | `{}` | Sets security context (at pod level). Can be configured also with `global.podSecurityContext` | +| priorityClassName | string | `""` | Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` | +| proxy | string | `""` | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` | +| rbac.create | bool | `true` | Specifies whether RBAC resources should be created | +| resources | object | `{}` | | +| serviceAccount.annotations | object | `{}` | Add these annotations to the service account we create. Can be configured also with `global.serviceAccount.annotations` | +| serviceAccount.create | bool | `true` | Configures if the service account should be created or not. Can be configured also with `global.serviceAccount.create` | +| serviceAccount.name | string | `nil` | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. Can be configured also with `global.serviceAccount.name` | +| tolerations | list | `[]` | Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` | +| verboseLog | bool | false | Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` | + +## Maintainers + +* [alvarocabanas](https://github.com/alvarocabanas) +* [carlossscastro](https://github.com/carlossscastro) +* [sigilioso](https://github.com/sigilioso) +* [gsanchezgavier](https://github.com/gsanchezgavier) +* [kang-makes](https://github.com/kang-makes) +* [marcsanmi](https://github.com/marcsanmi) +* [paologallinaharbur](https://github.com/paologallinaharbur) +* [roobre](https://github.com/roobre) diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md.gotmpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md.gotmpl new file mode 100644 index 000000000..5c1da4577 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/README.md.gotmpl @@ -0,0 +1,83 @@ +{{ template "chart.header" . }} +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +# Helm installation + +You can install this chart using [`nri-bundle`](https://github.com/newrelic/helm-charts/tree/master/charts/nri-bundle) located in the +[helm-charts repository](https://github.com/newrelic/helm-charts) or directly from this repository by adding this Helm repository: + +```shell +helm repo add nri-prometheus https://newrelic.github.io/nri-prometheus +helm upgrade --install newrelic-prometheus nri-prometheus/nri-prometheus -f your-custom-values.yaml +``` + +{{ template "chart.sourcesSection" . }} + +## Scraping services and endpoints + +When a service is labeled or annotated with `scrape_enabled_label` (defaults to `prometheus.io/scrape`), +`nri-prometheus` will attempt to hit the service directly, rather than the endpoints behind it. + +This is the default behavior for compatibility reasons, but is known to cause issues if more than one endpoint +is behind the service, as metric queries will be load-balanced as well leading to inaccurate histograms. + +In order to change this behaviour set `scrape_endpoints` to `true` and `scrape_services` to `false`. +This will instruct `nri-prometheus` to scrape the underlying endpoints, as Prometheus server does. + +Existing users that are switching to this behavior should note that, depending on the number of endpoints +behind the services in the cluster the load and the metrics reported by those, data ingestion might see +an increase when flipping this option. Resource requirements might also be impacted, again depending on the number of new targets. + +While it is technically possible to set both `scrape_services` and `scrape_endpoints` to true, we do no recommend +doing so as it will lead to redundant metrics being processed, + +## Values managed globally + +This chart implements the [New Relic's common Helm library](https://github.com/newrelic/helm-charts/tree/master/library/common-library) which +means that it honors a wide range of defaults and globals common to most New Relic Helm charts. + +Options that can be defined globally include `affinity`, `nodeSelector`, `tolerations`, `proxy` and others. The full list can be found at +[user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md). + +## Chart particularities + +### Low data mode +See this snippet from the `values.yaml` file: +```yaml +global: + lowDataMode: false +lowDataMode: false +``` + +To reduce the amount ot metrics we send to New Relic, enabling the `lowDataMode` will add [these transformations](static/lowdatamodedefaults.yaml): +```yaml +transformations: + - description: "Low data mode defaults" + ignore_metrics: + # Ignore the following metrics. + # These metrics are already collected by the New Relic Kubernetes Integration. + - prefixes: + - kube_ + - container_ + - machine_ + - cadvisor_ +``` + +{{ template "chart.valuesSection" . }} + +{{ if .Maintainers }} +## Maintainers +{{ range .Maintainers }} +{{- if .Name }} +{{- if .Url }} +* [{{ .Name }}]({{ .Url }}) +{{- else }} +* {{ .Name }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/.helmignore b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/.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/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/Chart.yaml new file mode 100644 index 000000000..f2ee5497e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +description: Provides helpers to provide consistency on all the charts +keywords: +- newrelic +- chart-library +maintainers: +- name: juanjjaramillo + url: https://github.com/juanjjaramillo +- name: csongnr + url: https://github.com/csongnr +- name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR +- name: kang-makes + url: https://github.com/kang-makes +name: common-library +type: library +version: 1.3.0 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/DEVELOPERS.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/DEVELOPERS.md new file mode 100644 index 000000000..7208c673e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/DEVELOPERS.md @@ -0,0 +1,747 @@ +# Functions/templates documented for chart writers +Here is some rough documentation separated by the file that contains the function, the function +name and how to use it. We are not covering functions that start with `_` (e.g. +`newrelic.common.license._licenseKey`) because they are used internally by this library for +other helpers. Helm does not have the concept of "public" or "private" functions/templates so +this is a convention of ours. + +## _naming.tpl +These functions are used to name objects. + +### `newrelic.common.naming.name` +This is the same as the idiomatic `CHART-NAME.name` that is created when you use `helm create`. + +It honors `.Values.nameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.name" . }} +``` + +### `newrelic.common.naming.fullname` +This is the same as the idiomatic `CHART-NAME.fullname` that is created when you use `helm create` + +It honors `.Values.fullnameOverride`. + +Usage: +```mustache +{{ include "newrelic.common.naming.fullname" . }} +``` + +### `newrelic.common.naming.chart` +This is the same as the idiomatic `CHART-NAME.chart` that is created when you use `helm create`. + +It is mostly useless for chart writers. It is used internally for templating the labels but there +is no reason to keep it "private". + +Usage: +```mustache +{{ include "newrelic.common.naming.chart" . }} +``` + +### `newrelic.common.naming.truncateToDNS` +This is a useful template that could be used to trim a string to 63 chars and does not end with a dash (`-`). +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $truncatedName := include "newrelic.common.naming.truncateToDNS" $nameToTruncate }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-string-that-should-be */ -}} +``` + +### `newrelic.common.naming.truncateToDNSWithSuffix` +This template function is the same as the above but instead of receiving a string you should give a `dict` +with a `name` and a `suffix`. This function will join them with a dash (`-`) and trim the `name` so the +result of `name-suffix` is no more than 63 chars + +Usage: +```mustache +{{ $nameToTruncate := "a-really-really-really-really-REALLY-long-string-that-should-be-truncated-because-it-is-enought-long-to-brak-something" +{{- $suffix := "A-NOT-SO-LONG-SUFFIX" }} +{{- $truncatedName := include "truncateToDNSWithSuffix" (dict "name" $nameToTruncate "suffix" $suffix) }} +{{- $truncatedName }} +{{- /* This should print: a-really-really-really-really-REALLY-long-A-NOT-SO-LONG-SUFFIX */ -}} +``` + + + +## _labels.tpl +### `newrelic.common.labels`, `newrelic.common.labels.selectorLabels` and `newrelic.common.labels.podLabels` +These are functions that are used to label objects. They are configured by this `values.yaml` +```yaml +global: + podLabels: {} # included in all the pods of all the charts that implement this library + labels: {} # included in all the objects of all the charts that implement this library +podLabels: {} # included in all the pods of this chart +labels: {} # included in all the objects of this chart +``` + +label maps are merged from global to local values. + +And chart writer should use them like this: +```mustache +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "newrelic.common.labels.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} +``` + +`newrelic.common.labels.podLabels` includes `newrelic.common.labels.selectorLabels` automatically. + + + +## _priority-class-name.tpl +### `newrelic.common.priorityClassName` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + priorityClassName: "" +priorityClassName: "" +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `priorityClassName` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} +``` + + + +## _hostnetwork.tpl +### `newrelic.common.hostNetwork` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + hostNetwork: # Note that this is empty (nil) +hostNetwork: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `hostNetwork` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.hostNetwork" . }} + hostNetwork: {{ . }} + {{- end }} +``` + +### `newrelic.common.hostNetwork.value` +This function is an abstraction of the function above but this returns directly "true" or "false". + +Be careful with using this with an `if` as Helm does evaluate "false" (string) as `true`. + +Usage (example in a pod spec): +```mustache +spec: + hostNetwork: {{ include "newrelic.common.hostNetwork.value" . }} +``` + + + +## _dnsconfig.tpl +### `newrelic.common.dnsConfig` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + dnsConfig: {} +dnsConfig: {} +``` + +Be careful: chart writers should put an empty string (or any kind of Helm falsiness) for this +library to work properly. If in your values a non-falsy `dnsConfig` is found, the global +one is going to be always ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _images.tpl +These functions help us to deal with how images are templated. This allows setting `registries` +where to fetch images globally while being flexible enough to fit in different maps of images +and deployments with one or more images. This is the example of a complex `values.yaml` that +we are going to use during the documentation of these functions: + +```yaml +global: + images: + registry: nexus-3-instance.internal.clients-domain.tld +jobImage: + registry: # defaults to "example.tld" when empty in these examples + repository: ingress-nginx/kube-webhook-certgen + tag: v1.1.1 + pullPolicy: IfNotPresent + pullSecrets: [] +images: + integration: + registry: + repository: newrelic/nri-kube-events + tag: 1.8.0 + pullPolicy: IfNotPresent + agent: + registry: + repository: newrelic/k8s-events-forwarder + tag: 1.22.0 + pullPolicy: IfNotPresent + pullSecrets: [] +``` + +### `newrelic.common.images.image` +This will return a string with the image ready to be downloaded that includes the registry, the image and the tag. +`defaultRegistry` is used to keep `registry` field empty in `values.yaml` so you can override the image using +`global.images.registry`, your local `jobImage.registry` and be able to fallback to a registry that is not `docker.io` +(Or the default repository that the client could have set in the CRI). + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.image" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.registry` +It returns the registry from the global or local values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For the integration */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.images.agent "context" .) }} +{{- /* For jobImage */}} +{{ include "newrelic.common.images.registry" ( dict "defaultRegistry" "example.tld" "imageRoot" .Values.jobImage "context" .) }} +``` + +### `newrelic.common.images.repository` +It returns the image from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.repository" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.tag` +It returns the image's tag from the values. You should avoid using this helper to create your image +URL and use `newrelic.common.images.image` instead, but it is there to be used in case it is needed. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.jobImage "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.integration "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.images.agent "context" .) }} +``` + +### `newrelic.common.images.renderPullSecrets` +If returns a merged map that contains the pull secrets from the global configuration and the local one. + +Usage: +```mustache +{{- /* For jobImage */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.jobImage.pullSecrets "context" .) }} +{{- /* For the integration */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +{{- /* For the agent */}} +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" .Values.images.pullSecrets "context" .) }} +``` + + + +## _serviceaccount.tpl +These functions are used to evaluate if the service account should be created, with which name and add annotations to it. + +The functions that the common library has implemented for service accounts are: +* `newrelic.common.serviceAccount.create` +* `newrelic.common.serviceAccount.name` +* `newrelic.common.serviceAccount.annotations` + +Usage: +```mustache +{{- if include "newrelic.common.serviceAccount.create" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +``` + + + +## _affinity.tpl, _nodeselector.tpl and _tolerations.tpl +These three files are almost the same and they follow the idiomatic way of `helm create`. + +Each function also looks if there is a global value like the other helpers. +```yaml +global: + affinity: {} + nodeSelector: {} + tolerations: [] +affinity: {} +nodeSelector: {} +tolerations: [] +``` + +The values here are replaced instead of be merged. If a value at root level is found, the global one is ignored. + +Usage (example in a pod spec): +```mustache +spec: + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 4 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 4 }} + {{- end }} +``` + + + +## _agent-config.tpl +### `newrelic.common.agentConfig.defaults` +This returns a YAML that the agent can use directly as a config that includes other options from the values file like verbose mode, +custom attributes, FedRAMP and such. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + name: {{ include newrelic.common.naming.truncateToDNSWithSuffix (dict "name" (include "newrelic.common.naming.fullname" .) suffix "agent-config") }} + namespace: {{ .Release.Namespace }} +data: + newrelic-infra.yml: |- + # This is the configuration file for the infrastructure agent. See: + # https://docs.newrelic.com/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings/ + {{- include "newrelic.common.agentConfig.defaults" . | nindent 4 }} +``` + + + +## _cluster.tpl +### `newrelic.common.cluster` +Returns the cluster name + +Usage: +```mustache +{{ include "newrelic.common.cluster" . }} +``` + + + +## _custom-attributes.tpl +### `newrelic.common.customAttributes` +Return custom attributes in YAML format. + +Usage: +```mustache +apiVersion: v1 +kind: ConfigMap +metadata: + name: example +data: + custom-attributes.yaml: | + {{- include "newrelic.common.customAttributes" . | nindent 4 }} + custom-attributes.json: | + {{- include "newrelic.common.customAttributes" . | fromYaml | toJson | nindent 4 }} +``` + + + +## _fedramp.tpl +### `newrelic.common.fedramp.enabled` +Returns true if FedRAMP is enabled or an empty string if not. It can be safely used in conditionals as an empty string is a Helm falsiness. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled" . }} +``` + +### `newrelic.common.fedramp.enabled.value` +Returns true if FedRAMP is enabled or false if not. This is to have the value of FedRAMP ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.fedramp.enabled.value" . }} +``` + + + +## _license.tpl +### `newrelic.common.license.secretName` and ### `newrelic.common.license.secretKeyName` +Returns the secret and key inside the secret where to read the license key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the license key. + +To create the secret use `newrelic.common.license.secret`. + +Usage: +```mustache +{{- if and (.Values.controlPlane.enabled) (not (include "newrelic.fargate" .)) }} +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + containers: + - name: agent + env: + - name: "NRIA_LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} +``` + + + +## _license_secret.tpl +### `newrelic.common.license.secret` +This function templates the secret that is used by agents and integrations with the license Key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any license key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.license.secret" . -}} +``` + + + +## _insights.tpl +### `newrelic.common.insightsKey.secretName` and ### `newrelic.common.insightsKey.secretKeyName` +Returns the secret and key inside the secret where to read the insights key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.insightsKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "INSIGHTS_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + key: {{ include "newrelic.common.insightsKey.secretKeyName" . }} +``` + + + +## _insights_secret.tpl +### `newrelic.common.insightsKey.secret` +This function templates the secret that is used by agents and integrations with the insights key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any insights key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.insightsKey.secret" . -}} +``` + + + +## _userkey.tpl +### `newrelic.common.userKey.secretName` and ### `newrelic.common.userKey.secretKeyName` +Returns the secret and key inside the secret where to read a user key. + +The common library will take care of using a user-provided custom secret or creating a secret that contains the insights key. + +To create the secret use `newrelic.common.userKey.secret`. + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: statsd +spec: + containers: + - name: statsd + env: + - name: "API_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.userKey.secretName" . }} + key: {{ include "newrelic.common.userKey.secretKeyName" . }} +``` + + + +## _userkey_secret.tpl +### `newrelic.common.userKey.secret` +This function templates the secret that is used by agents and integrations with a user key provided by the user. It will +template nothing (empty string) if the user provides a custom pair of secret name and key. + +This template also fails in case the user has not provided any API key or custom secret so no safety checks have to be done +by chart writers. + +You just must have a template with these two lines: +```mustache +{{- /* Common library will take care of creating the secret or not. */ -}} +{{- include "newrelic.common.userKey.secret" . -}} +``` + + + +## _region.tpl +### `newrelic.common.region.validate` +Given a string, return a normalized name for the region if valid. + +This function does not need the context of the chart, only the value to be validated. The region returned +honors the region [definition of the newrelic-client-go implementation](https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21) +so (as of 2024/09/14) it returns the region as "US", "EU", "Staging", or "Local". + +In case the region provided does not match these 4, the helper calls `fail` and abort the templating. + +Usage: +```mustache +{{ include "newrelic.common.region.validate" "us" }} +``` + +### `newrelic.common.region` +It reads global and local variables for `region`: +```yaml +global: + region: # Note that this can be empty (nil) or "" (empty string) +region: # Note that this can be empty (nil) or "" (empty string) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in your +values a `region` is defined, the global one is going to be always ignored. + +This function gives protection so it enforces users to give the license key as a value in their +`values.yaml` or specify a global or local `region` value. To understand how the `region` value +works, read the documentation of `newrelic.common.region.validate`. + +The function will change the region from US, EU or Staging based of the license key and the +`nrStaging` toggle. Whichever region is computed from the license/toggle can be overridden by +the `region` value. + +Usage: +```mustache +{{ include "newrelic.common.region" . }} +``` + + + +## _low-data-mode.tpl +### `newrelic.common.lowDataMode` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + lowDataMode: # Note that this is empty (nil) +lowDataMode: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `lowdataMode` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.lowDataMode" . }} +``` + + + +## _privileged.tpl +### `newrelic.common.privileged` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + privileged: # Note that this is empty (nil) +privileged: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `privileged` is defined, the global one is going to be always ignored. + +Chart writers could override this and put directly a `true` in the `values.yaml` to override the +default of the common library. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.privileged" . }} +``` + +### `newrelic.common.privileged.value` +Returns true if privileged mode is enabled or false if not. This is to have the value of privileged ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.privileged.value" . }} +``` + + + +## _proxy.tpl +### `newrelic.common.proxy` +Returns the proxy URL configured by the user. + +Usage: +```mustache +{{ include "newrelic.common.proxy" . }} +``` + + + +## _security-context.tpl +Use these functions to share the security context among all charts. Useful in clusters that have security enforcing not to +use the root user (like OpenShift) or users that have an admission webhooks. + +The functions are: +* `newrelic.common.securityContext.container` +* `newrelic.common.securityContext.pod` + +Usage: +```mustache +apiVersion: v1 +kind: Pod +metadata: + name: example +spec: + spec: + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + + containers: + - name: example + {{- with include "nriKubernetes.securityContext.container" . }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} +``` + + + +## _staging.tpl +### `newrelic.common.nrStaging` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + nrStaging: # Note that this is empty (nil) +nrStaging: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `nrStaging` is defined, the global one is going to be always ignored. + +This function returns "true" of "" (empty string) so it can be used for evaluating conditionals. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging" . }} +``` + +### `newrelic.common.nrStaging.value` +Returns true if staging is enabled or false if not. This is to have the staging value ready to be templated. + +Usage: +```mustache +{{ include "newrelic.common.nrStaging.value" . }} +``` + + + +## _verbose-log.tpl +### `newrelic.common.verboseLog` +Like almost everything in this library, it reads global and local variables: +```yaml +global: + verboseLog: # Note that this is empty (nil) +verboseLog: # Note that this is empty (nil) +``` + +Be careful: chart writers should NOT PUT ANY VALUE for this library to work properly. If in you +values a `verboseLog` is defined, the global one is going to be always ignored. + +Usage: +```mustache +{{ include "newrelic.common.verboseLog" . }} +``` + +### `newrelic.common.verboseLog.valueAsBoolean` +Returns true if verbose is enabled or false if not. This is to have the verbose value ready to be templated as a boolean + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsBoolean" . }} +``` + +### `newrelic.common.verboseLog.valueAsInt` +Returns 1 if verbose is enabled or 0 if not. This is to have the verbose value ready to be templated as an integer + +Usage: +```mustache +{{ include "newrelic.common.verboseLog.valueAsInt" . }} +``` diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/README.md b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/README.md new file mode 100644 index 000000000..10f08ca67 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/README.md @@ -0,0 +1,106 @@ +# Helm Common library + +The common library is a way to unify the UX through all the Helm charts that implement it. + +The tooling suite that New Relic is huge and growing and this allows to set things globally +and locally for a single chart. + +## Documentation for chart writers + +If you are writing a chart that is going to use this library you can check the [developers guide](/library/common-library/DEVELOPERS.md) to see all +the functions/templates that we have implemented, what they do and how to use them. + +## Values managed globally + +We want to have a seamless experience through all the charts so we created this library that tries to standardize the behaviour +of all the charts. Sadly, because of the complexity of all these integrations, not all the charts behave exactly as expected. + +An example is `newrelic-infrastructure` that ignores `hostNetwork` in the control plane scraper because most of the users has the +control plane listening in the node to `localhost`. + +For each chart that has a special behavior (or further information of the behavior) there is a "chart particularities" section +in its README.md that explains which is the expected behavior. + +At the time of writing this, all the charts from `nri-bundle` except `newrelic-logging` and `synthetics-minion` implements this +library and honors global options as described in this document. + +Here is a list of global options: + +| Global keys | Local keys | Default | Merged[1](#values-managed-globally-1) | Description | +|-------------|------------|---------|--------------------------------------------------|-------------| +| global.cluster | cluster | `""` | | Name of the Kubernetes cluster monitored | +| global.licenseKey | licenseKey | `""` | | This set this license key to use | +| global.customSecretName | customSecretName | `""` | | In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there | +| global.customSecretLicenseKey | customSecretLicenseKey | `""` | | In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located | +| global.podLabels | podLabels | `{}` | yes | Additional labels for chart pods | +| global.labels | labels | `{}` | yes | Additional labels for chart objects | +| global.priorityClassName | priorityClassName | `""` | | Sets pod's priorityClassName | +| global.hostNetwork | hostNetwork | `false` | | Sets pod's hostNetwork | +| global.dnsConfig | dnsConfig | `{}` | | Sets pod's dnsConfig | +| global.images.registry | See [Further information](#values-managed-globally-2) | `""` | | Changes the registry where to get the images. Useful when there is an internal image cache/proxy | +| global.images.pullSecrets | See [Further information](#values-managed-globally-2) | `[]` | yes | Set secrets to be able to fetch images | +| global.podSecurityContext | podSecurityContext | `{}` | | Sets security context (at pod level) | +| global.containerSecurityContext | containerSecurityContext | `{}` | | Sets security context (at container level) | +| global.affinity | affinity | `{}` | | Sets pod/node affinities | +| global.nodeSelector | nodeSelector | `{}` | | Sets pod's node selector | +| global.tolerations | tolerations | `[]` | | Sets pod's tolerations to node taints | +| global.serviceAccount.create | serviceAccount.create | `true` | | Configures if the service account should be created or not | +| global.serviceAccount.name | serviceAccount.name | name of the release | | Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. | +| global.serviceAccount.annotations | serviceAccount.annotations | `{}` | yes | Add these annotations to the service account we create | +| global.customAttributes | customAttributes | `{}` | | Adds extra attributes to the cluster and all the metrics emitted to the backend | +| global.fedramp | fedramp | `false` | | Enables FedRAMP | +| global.lowDataMode | lowDataMode | `false` | | Reduces number of metrics sent in order to reduce costs | +| global.privileged | privileged | Depends on the chart | | In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | +| global.proxy | proxy | `""` | | Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` | +| global.nrStaging | nrStaging | `false` | | Send the metrics to the staging backend. Requires a valid staging license key | +| global.verboseLog | verboseLog | `false` | | Sets the debug/trace logs to this integration or all integrations if it is set globally | + +### Further information + +#### 1. Merged + +Merged means that the values from global are not replaced by the local ones. Think in this example: +```yaml +global: + labels: + global: global + hostNetwork: true + nodeSelector: + global: global + +labels: + local: local +nodeSelector: + local: local +hostNetwork: false +``` + +This values will template `hostNetwork` to `false`, a map of labels `{ "global": "global", "local": "local" }` and a `nodeSelector` with +`{ "local": "local" }`. + +As Helm by default merges all the maps it could be confusing that we have two behaviors (merging `labels` and replacing `nodeSelector`) +the `values` from global to local. This is the rationale behind this: +* `hostNetwork` is templated to `false` because is overriding the value defined globally. +* `labels` are merged because the user may want to label all the New Relic pods at once and label other solution pods differently for + clarity' sake. +* `nodeSelector` does not merge as `labels` because could make it harder to overwrite/delete a selector that comes from global because + of the logic that Helm follows merging maps. + + +#### 2. Fine grain registries + +Some charts only have 1 image while others that can have 2 or more images. The local path for the registry can change depending +on the chart itself. + +As this is mostly unique per helm chart, you should take a look to the chart's values table (or directly to the `values.yaml` file to see all the +images that you can change. + +This should only be needed if you have an advanced setup that forces you to have granularity enough to force a proxy/cache registry per integration. + + + +#### 3. Privileged mode + +By default, from the common library, the privileged mode is set to false. But most of the helm charts require this to be true to fetch more +metrics so could see a true in some charts. The consequences of the privileged mode differ from one chart to another so for each chart that +honors the privileged mode toggle should be a section in the README explaining which is the behavior with it enabled or disabled. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl new file mode 100644 index 000000000..1b2636754 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_affinity.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod affinity */ -}} +{{- define "newrelic.common.affinity" -}} + {{- if .Values.affinity -}} + {{- toYaml .Values.affinity -}} + {{- else if .Values.global -}} + {{- if .Values.global.affinity -}} + {{- toYaml .Values.global.affinity -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl new file mode 100644 index 000000000..9c32861a0 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_agent-config.tpl @@ -0,0 +1,26 @@ +{{/* +This helper should return the defaults that all agents should have +*/}} +{{- define "newrelic.common.agentConfig.defaults" -}} +{{- if include "newrelic.common.verboseLog" . }} +log: + level: trace +{{- end }} + +{{- if (include "newrelic.common.nrStaging" . ) }} +staging: true +{{- end }} + +{{- with include "newrelic.common.proxy" . }} +proxy: {{ . | quote }} +{{- end }} + +{{- with include "newrelic.common.fedramp.enabled" . }} +fedramp: {{ . }} +{{- end }} + +{{- with fromYaml ( include "newrelic.common.customAttributes" . ) }} +custom_attributes: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl new file mode 100644 index 000000000..0197dd35a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_cluster.tpl @@ -0,0 +1,15 @@ +{{/* +Return the cluster +*/}} +{{- define "newrelic.common.cluster" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.cluster -}} + {{- .Values.cluster -}} +{{- else if $global.cluster -}} + {{- $global.cluster -}} +{{- else -}} + {{ fail "There is not cluster name definition set neither in `.global.cluster' nor `.cluster' in your values.yaml. Cluster name is required." }} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl new file mode 100644 index 000000000..92020719c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_custom-attributes.tpl @@ -0,0 +1,17 @@ +{{/* +This will render custom attributes as a YAML ready to be templated or be used with `fromYaml`. +*/}} +{{- define "newrelic.common.customAttributes" -}} +{{- $customAttributes := dict -}} + +{{- $global := index .Values "global" | default dict -}} +{{- if $global.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes $global.customAttributes -}} +{{- end -}} + +{{- if .Values.customAttributes -}} +{{- $customAttributes = mergeOverwrite $customAttributes .Values.customAttributes -}} +{{- end -}} + +{{- toYaml $customAttributes -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl new file mode 100644 index 000000000..d4e40aa8a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_dnsconfig.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod dnsConfig */ -}} +{{- define "newrelic.common.dnsConfig" -}} + {{- if .Values.dnsConfig -}} + {{- toYaml .Values.dnsConfig -}} + {{- else if .Values.global -}} + {{- if .Values.global.dnsConfig -}} + {{- toYaml .Values.global.dnsConfig -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl new file mode 100644 index 000000000..9df8d6b5e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_fedramp.tpl @@ -0,0 +1,25 @@ +{{- /* Defines the fedRAMP flag */ -}} +{{- define "newrelic.common.fedramp.enabled" -}} + {{- if .Values.fedramp -}} + {{- if .Values.fedramp.enabled -}} + {{- .Values.fedramp.enabled -}} + {{- end -}} + {{- else if .Values.global -}} + {{- if .Values.global.fedramp -}} + {{- if .Values.global.fedramp.enabled -}} + {{- .Values.global.fedramp.enabled -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + + + +{{- /* Return FedRAMP value directly ready to be templated */ -}} +{{- define "newrelic.common.fedramp.enabled.value" -}} +{{- if include "newrelic.common.fedramp.enabled" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl new file mode 100644 index 000000000..4cf017ef7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_hostnetwork.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the hostNetwork toggle. +This helper allows to override the global `.global.hostNetwork` with the value of `.hostNetwork`. +Returns "true" if `hostNetwork` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.hostNetwork" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} + +{{- /* +`get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs + +We also want only to return when this is true, returning `false` here will template "false" (string) when doing +an `(include "newrelic.common.hostNetwork" .)`, which is not an "empty string" so it is `true` if it is used +as an evaluation somewhere else. +*/ -}} +{{- if get .Values "hostNetwork" | kindIs "bool" -}} + {{- if .Values.hostNetwork -}} + {{- .Values.hostNetwork -}} + {{- end -}} +{{- else if get $global "hostNetwork" | kindIs "bool" -}} + {{- if $global.hostNetwork -}} + {{- $global.hostNetwork -}} + {{- end -}} +{{- end -}} +{{- end -}} + + +{{- /* +Abstraction of the hostNetwork toggle. +This helper abstracts the function "newrelic.common.hostNetwork" to return true or false directly. +*/ -}} +{{- define "newrelic.common.hostNetwork.value" -}} +{{- if include "newrelic.common.hostNetwork" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_images.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_images.tpl new file mode 100644 index 000000000..d4fb43290 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_images.tpl @@ -0,0 +1,94 @@ +{{- /* +Return the proper image name +{{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.image" -}} + {{- $registryName := include "newrelic.common.images.registry" ( dict "imageRoot" .imageRoot "defaultRegistry" .defaultRegistry "context" .context ) -}} + {{- $repositoryName := include "newrelic.common.images.repository" .imageRoot -}} + {{- $tag := include "newrelic.common.images.tag" ( dict "imageRoot" .imageRoot "context" .context) -}} + + {{- if $registryName -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag | quote -}} + {{- else -}} + {{- printf "%s:%s" $repositoryName $tag | quote -}} + {{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image registry +{{ include "newrelic.common.images.registry" ( dict "imageRoot" .Values.path.to.the.image "defaultRegistry" "your.private.registry.tld" "context" .) }} +*/ -}} +{{- define "newrelic.common.images.registry" -}} +{{- $globalRegistry := "" -}} +{{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- with .context.Values.global.images.registry -}} + {{- $globalRegistry = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- $localRegistry := "" -}} +{{- if .imageRoot.registry -}} + {{- $localRegistry = .imageRoot.registry -}} +{{- end -}} + +{{- $registry := $localRegistry | default $globalRegistry | default .defaultRegistry -}} +{{- if $registry -}} + {{- $registry -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Return the proper image repository +{{ include "newrelic.common.images.repository" .Values.path.to.the.image }} +*/ -}} +{{- define "newrelic.common.images.repository" -}} + {{- .repository -}} +{{- end -}} + + + +{{- /* +Return the proper image tag +{{ include "newrelic.common.images.tag" ( dict "imageRoot" .Values.path.to.the.image "context" .) }} +*/ -}} +{{- define "newrelic.common.images.tag" -}} + {{- .imageRoot.tag | default .context.Chart.AppVersion | toString -}} +{{- end -}} + + + +{{- /* +Return the proper Image Pull Registry Secret Names evaluating values as templates +{{ include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.path.to.the.images.pullSecrets1, .Values.path.to.the.images.pullSecrets2) "context" .) }} +*/ -}} +{{- define "newrelic.common.images.renderPullSecrets" -}} + {{- $flatlist := list }} + + {{- if .context.Values.global -}} + {{- if .context.Values.global.images -}} + {{- if .context.Values.global.images.pullSecrets -}} + {{- range .context.Values.global.images.pullSecrets -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range .pullSecrets -}} + {{- if not (empty .) -}} + {{- range . -}} + {{- $flatlist = append $flatlist . -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if $flatlist -}} + {{- toYaml $flatlist -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights.tpl new file mode 100644 index 000000000..895c37732 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the Insights Key. +*/}} +{{- define "newrelic.common.insightsKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "insightskey" ) -}} +{{- include "newrelic.common.insightsKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +*/}} +{{- define "newrelic.common.insightsKey.secretKeyName" -}} +{{- include "newrelic.common.insightsKey._customSecretKey" . | default "insightsKey" -}} +{{- end -}} + +{{/* +Return local insightsKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._licenseKey" -}} +{{- if .Values.insightsKey -}} + {{- .Values.insightsKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.insightsKey -}} + {{- .Values.global.insightsKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the Insights Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretName" -}} +{{- if .Values.customInsightsKeySecretName -}} + {{- .Values.customInsightsKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretName -}} + {{- .Values.global.customInsightsKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the Insights Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.insightsKey._customSecretKey" -}} +{{- if .Values.customInsightsKeySecretKey -}} + {{- .Values.customInsightsKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customInsightsKeySecretKey }} + {{- .Values.global.customInsightsKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl new file mode 100644 index 000000000..556caa6ca --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_insights_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the insights key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.insightsKey.secret" }} +{{- if not (include "newrelic.common.insightsKey._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.insightsKey._licenseKey" .) }} + {{- fail "You must specify a insightsKey or a customInsightsSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.insightsKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.insightsKey.secretKeyName" . }}: {{ include "newrelic.common.insightsKey._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_labels.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_labels.tpl new file mode 100644 index 000000000..b02594828 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_labels.tpl @@ -0,0 +1,54 @@ +{{/* +This will render the labels that should be used in all the manifests used by the helm chart. +*/}} +{{- define "newrelic.common.labels" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- $chart := dict "helm.sh/chart" (include "newrelic.common.naming.chart" . ) -}} +{{- $managedBy := dict "app.kubernetes.io/managed-by" .Release.Service -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $labels := mustMergeOverwrite $chart $managedBy $selectorLabels -}} +{{- if .Chart.AppVersion -}} +{{- $labels = mustMergeOverwrite $labels (dict "app.kubernetes.io/version" .Chart.AppVersion) -}} +{{- end -}} + +{{- $globalUserLabels := $global.labels | default dict -}} +{{- $localUserLabels := .Values.labels | default dict -}} + +{{- $labels = mustMergeOverwrite $labels $globalUserLabels $localUserLabels -}} + +{{- toYaml $labels -}} +{{- end -}} + + + +{{/* +This will render the labels that should be used in deployments/daemonsets template pods as a selector. +*/}} +{{- define "newrelic.common.labels.selectorLabels" -}} +{{- $name := dict "app.kubernetes.io/name" ( include "newrelic.common.naming.name" . ) -}} +{{- $instance := dict "app.kubernetes.io/instance" .Release.Name -}} + +{{- $selectorLabels := mustMergeOverwrite $name $instance -}} + +{{- toYaml $selectorLabels -}} +{{- end }} + + + +{{/* +Pod labels +*/}} +{{- define "newrelic.common.labels.podLabels" -}} +{{- $selectorLabels := fromYaml (include "newrelic.common.labels.selectorLabels" . ) -}} + +{{- $global := index .Values "global" | default dict -}} +{{- $globalPodLabels := $global.podLabels | default dict }} + +{{- $localPodLabels := .Values.podLabels | default dict }} + +{{- $podLabels := mustMergeOverwrite $selectorLabels $globalPodLabels $localPodLabels -}} + +{{- toYaml $podLabels -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license.tpl new file mode 100644 index 000000000..cb349f6bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license.tpl @@ -0,0 +1,68 @@ +{{/* +Return the name of the secret holding the License Key. +*/}} +{{- define "newrelic.common.license.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "license" ) -}} +{{- include "newrelic.common.license._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +*/}} +{{- define "newrelic.common.license.secretKeyName" -}} +{{- include "newrelic.common.license._customSecretKey" . | default "licenseKey" -}} +{{- end -}} + +{{/* +Return local licenseKey if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._licenseKey" -}} +{{- if .Values.licenseKey -}} + {{- .Values.licenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.licenseKey -}} + {{- .Values.global.licenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the License Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretName" -}} +{{- if .Values.customSecretName -}} + {{- .Values.customSecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretName -}} + {{- .Values.global.customSecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the License Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._customSecretKey" -}} +{{- if .Values.customSecretLicenseKey -}} + {{- .Values.customSecretLicenseKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customSecretLicenseKey }} + {{- .Values.global.customSecretLicenseKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the user set a custom secret for the license. +This helper is for internal use. +*/}} +{{- define "newrelic.common.license._usesCustomSecret" -}} +{{- if or (include "newrelic.common.license._customSecretName" .) (include "newrelic.common.license._customSecretKey" .) -}} +true +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl new file mode 100644 index 000000000..610a0a337 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_license_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the license key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.license.secret" }} +{{- if not (include "newrelic.common.license._customSecretName" .) }} +{{- /* Fail if licenseKey is empty and required: */ -}} +{{- if not (include "newrelic.common.license._licenseKey" .) }} + {{- fail "You must specify a licenseKey or a customSecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.license.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.license.secretKeyName" . }}: {{ include "newrelic.common.license._licenseKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl new file mode 100644 index 000000000..3dd55ef2f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_low-data-mode.tpl @@ -0,0 +1,26 @@ +{{- /* +Abstraction of the lowDataMode toggle. +This helper allows to override the global `.global.lowDataMode` with the value of `.lowDataMode`. +Returns "true" if `lowDataMode` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.lowDataMode" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "lowDataMode" | kindIs "bool") -}} + {{- if .Values.lowDataMode -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.lowDataMode" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.lowDataMode -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "lowDataMode" | kindIs "bool" -}} + {{- if $global.lowDataMode -}} + {{- $global.lowDataMode -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_naming.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_naming.tpl new file mode 100644 index 000000000..19fa92648 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_naming.tpl @@ -0,0 +1,73 @@ +{{/* +This is an function to be called directly with a string just to truncate strings to +63 chars because some Kubernetes name fields are limited to that. +*/}} +{{- define "newrelic.common.naming.truncateToDNS" -}} +{{- . | trunc 63 | trimSuffix "-" }} +{{- end }} + + + +{{- /* +Given a name and a suffix returns a 'DNS Valid' which always include the suffix, truncating the name if needed. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If suffix is too long it gets truncated but it always takes precedence over name, so a 63 chars suffix would suppress the name. +Usage: +{{ include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" "" "suffix" "my-suffix" ) }} +*/ -}} +{{- define "newrelic.common.naming.truncateToDNSWithSuffix" -}} +{{- $suffix := (include "newrelic.common.naming.truncateToDNS" .suffix) -}} +{{- $maxLen := (max (sub 63 (add1 (len $suffix))) 0) -}} {{- /* We prepend "-" to the suffix so an additional character is needed */ -}} + +{{- $newName := .name | trunc ($maxLen | int) | trimSuffix "-" -}} +{{- if $newName -}} +{{- printf "%s-%s" $newName $suffix -}} +{{- else -}} +{{ $suffix }} +{{- end -}} + +{{- end -}} + + + +{{/* +Expand the name of the chart. +Uses the Chart name by default if nameOverride is not set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.name" -}} +{{- $name := .Values.nameOverride | default .Chart.Name -}} +{{- include "newrelic.common.naming.truncateToDNS" $name -}} +{{- end }} + + + +{{/* +Create a default fully qualified app name. +By default the full name will be "" just in if it has the chart name included in that, if not +it will be concatenated like "-". This could change if fullnameOverride or +nameOverride are set. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "newrelic.common.naming.fullname" -}} +{{- $name := include "newrelic.common.naming.name" . -}} + +{{- if .Values.fullnameOverride -}} + {{- $name = .Values.fullnameOverride -}} +{{- else if not (contains $name .Release.Name) -}} + {{- $name = printf "%s-%s" .Release.Name $name -}} +{{- end -}} + +{{- include "newrelic.common.naming.truncateToDNS" $name -}} + +{{- end -}} + + + +{{/* +Create chart name and version as used by the chart label. +This function should not be used for naming objects. Use "common.naming.{name,fullname}" instead. +*/}} +{{- define "newrelic.common.naming.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl new file mode 100644 index 000000000..d48887341 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_nodeselector.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod nodeSelector */ -}} +{{- define "newrelic.common.nodeSelector" -}} + {{- if .Values.nodeSelector -}} + {{- toYaml .Values.nodeSelector -}} + {{- else if .Values.global -}} + {{- if .Values.global.nodeSelector -}} + {{- toYaml .Values.global.nodeSelector -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl new file mode 100644 index 000000000..50182b734 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_priority-class-name.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the pod priorityClassName */ -}} +{{- define "newrelic.common.priorityClassName" -}} + {{- if .Values.priorityClassName -}} + {{- .Values.priorityClassName -}} + {{- else if .Values.global -}} + {{- if .Values.global.priorityClassName -}} + {{- .Values.global.priorityClassName -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl new file mode 100644 index 000000000..f3ae814dd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_privileged.tpl @@ -0,0 +1,28 @@ +{{- /* +This is a helper that returns whether the chart should assume the user is fine deploying privileged pods. +*/ -}} +{{- define "newrelic.common.privileged" -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists. */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if get .Values "privileged" | kindIs "bool" -}} + {{- if .Values.privileged -}} + {{- .Values.privileged -}} + {{- end -}} +{{- else if get $global "privileged" | kindIs "bool" -}} + {{- if $global.privileged -}} + {{- $global.privileged -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* Return directly "true" or "false" based in the exist of "newrelic.common.privileged" */ -}} +{{- define "newrelic.common.privileged.value" -}} +{{- if include "newrelic.common.privileged" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl new file mode 100644 index 000000000..60f34c7ec --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_proxy.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the proxy */ -}} +{{- define "newrelic.common.proxy" -}} + {{- if .Values.proxy -}} + {{- .Values.proxy -}} + {{- else if .Values.global -}} + {{- if .Values.global.proxy -}} + {{- .Values.global.proxy -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_region.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_region.tpl new file mode 100644 index 000000000..bdcacf323 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_region.tpl @@ -0,0 +1,74 @@ +{{/* +Return the region that is being used by the user +*/}} +{{- define "newrelic.common.region" -}} +{{- if and (include "newrelic.common.license._usesCustomSecret" .) (not (include "newrelic.common.region._fromValues" .)) -}} + {{- fail "This Helm Chart is not able to compute the region. You must specify a .global.region or .region if the license is set using a custom secret." -}} +{{- end -}} + +{{- /* Defaults */ -}} +{{- $region := "us" -}} +{{- if include "newrelic.common.nrStaging" . -}} + {{- $region = "staging" -}} +{{- else if include "newrelic.common.region._isEULicenseKey" . -}} + {{- $region = "eu" -}} +{{- end -}} + +{{- include "newrelic.common.region.validate" (include "newrelic.common.region._fromValues" . | default $region ) -}} +{{- end -}} + + + +{{/* +Returns the region from the values if valid. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. + +Usage: `include "newrelic.common.region.validate" "us"` +*/}} +{{- define "newrelic.common.region.validate" -}} +{{- /* Ref: https://github.com/newrelic/newrelic-client-go/blob/cbe3e4cf2b95fd37095bf2ffdc5d61cffaec17e2/pkg/region/region_constants.go#L8-L21 */ -}} +{{- $region := . | lower -}} +{{- if eq $region "us" -}} + US +{{- else if eq $region "eu" -}} + EU +{{- else if eq $region "staging" -}} + Staging +{{- else if eq $region "local" -}} + Local +{{- else -}} + {{- fail (printf "the region provided is not valid: %s not in \"US\" \"EU\" \"Staging\" \"Local\"" .) -}} +{{- end -}} +{{- end -}} + + + +{{/* +Returns the region from the values. This only return the value from the `values.yaml`. +More intelligence should be used to compute the region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._fromValues" -}} +{{- if .Values.region -}} + {{- .Values.region -}} +{{- else if .Values.global -}} + {{- if .Values.global.region -}} + {{- .Values.global.region -}} + {{- end -}} +{{- end -}} +{{- end -}} + + + +{{/* +Return empty string (falsehood) or "true" if the license is for EU region. +This helper is for internal use. +*/}} +{{- define "newrelic.common.region._isEULicenseKey" -}} +{{- if not (include "newrelic.common.license._usesCustomSecret" .) -}} + {{- $license := include "newrelic.common.license._licenseKey" . -}} + {{- if hasPrefix "eu" $license -}} + true + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl new file mode 100644 index 000000000..9edfcabfd --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_security-context.tpl @@ -0,0 +1,23 @@ +{{- /* Defines the container securityContext context */ -}} +{{- define "newrelic.common.securityContext.container" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.containerSecurityContext -}} + {{- toYaml .Values.containerSecurityContext -}} +{{- else if $global.containerSecurityContext -}} + {{- toYaml $global.containerSecurityContext -}} +{{- end -}} +{{- end -}} + + + +{{- /* Defines the pod securityContext context */ -}} +{{- define "newrelic.common.securityContext.pod" -}} +{{- $global := index .Values "global" | default dict -}} + +{{- if .Values.podSecurityContext -}} + {{- toYaml .Values.podSecurityContext -}} +{{- else if $global.podSecurityContext -}} + {{- toYaml $global.podSecurityContext -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl new file mode 100644 index 000000000..2d352f6ea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_serviceaccount.tpl @@ -0,0 +1,90 @@ +{{- /* Defines if the service account has to be created or not */ -}} +{{- define "newrelic.common.serviceAccount.create" -}} +{{- $valueFound := false -}} + +{{- /* Look for a global creation of a service account */ -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "create" | kindIs "bool") -}} + {{- $valueFound = true -}} + {{- if .Values.serviceAccount.create -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.serviceAccount.name" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.serviceAccount.create -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* Look for a local creation of a service account */ -}} +{{- if not $valueFound -}} + {{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} + {{- $global := index .Values "global" | default dict -}} + {{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "create" | kindIs "bool" -}} + {{- $valueFound = true -}} + {{- if $global.serviceAccount.create -}} + {{- $global.serviceAccount.create -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- /* In case no serviceAccount value has been found, default to "true" */ -}} +{{- if not $valueFound -}} +true +{{- end -}} +{{- end -}} + + + +{{- /* Defines the name of the service account */ -}} +{{- define "newrelic.common.serviceAccount.name" -}} +{{- $localServiceAccount := "" -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if (get .Values.serviceAccount "name" | kindIs "string") -}} + {{- $localServiceAccount = .Values.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := "" -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "name" | kindIs "string" -}} + {{- $globalServiceAccount = $global.serviceAccount.name -}} + {{- end -}} +{{- end -}} + +{{- if (include "newrelic.common.serviceAccount.create" .) -}} + {{- $localServiceAccount | default $globalServiceAccount | default (include "newrelic.common.naming.fullname" .) -}} +{{- else -}} + {{- $localServiceAccount | default $globalServiceAccount | default "default" -}} +{{- end -}} +{{- end -}} + + + +{{- /* Merge the global and local annotations for the service account */ -}} +{{- define "newrelic.common.serviceAccount.annotations" -}} +{{- $localServiceAccount := dict -}} +{{- if get .Values "serviceAccount" | kindIs "map" -}} + {{- if get .Values.serviceAccount "annotations" -}} + {{- $localServiceAccount = .Values.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $globalServiceAccount := dict -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "serviceAccount" | kindIs "map" -}} + {{- if get $global.serviceAccount "annotations" -}} + {{- $globalServiceAccount = $global.serviceAccount.annotations -}} + {{- end -}} +{{- end -}} + +{{- $merged := mustMergeOverwrite $globalServiceAccount $localServiceAccount -}} + +{{- if $merged -}} + {{- toYaml $merged -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_staging.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_staging.tpl new file mode 100644 index 000000000..bd9ad09bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_staging.tpl @@ -0,0 +1,39 @@ +{{- /* +Abstraction of the nrStaging toggle. +This helper allows to override the global `.global.nrStaging` with the value of `.nrStaging`. +Returns "true" if `nrStaging` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.nrStaging" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "nrStaging" | kindIs "bool") -}} + {{- if .Values.nrStaging -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.nrStaging" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.nrStaging -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "nrStaging" | kindIs "bool" -}} + {{- if $global.nrStaging -}} + {{- $global.nrStaging -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Returns "true" of "false" directly instead of empty string (Helm falsiness) based on the exit of "newrelic.common.nrStaging" +*/ -}} +{{- define "newrelic.common.nrStaging.value" -}} +{{- if include "newrelic.common.nrStaging" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl new file mode 100644 index 000000000..e016b38e2 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_tolerations.tpl @@ -0,0 +1,10 @@ +{{- /* Defines the Pod tolerations */ -}} +{{- define "newrelic.common.tolerations" -}} + {{- if .Values.tolerations -}} + {{- toYaml .Values.tolerations -}} + {{- else if .Values.global -}} + {{- if .Values.global.tolerations -}} + {{- toYaml .Values.global.tolerations -}} + {{- end -}} + {{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey.tpl new file mode 100644 index 000000000..982ea8e09 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey.tpl @@ -0,0 +1,56 @@ +{{/* +Return the name of the secret holding the API Key. +*/}} +{{- define "newrelic.common.userKey.secretName" -}} +{{- $default := include "newrelic.common.naming.truncateToDNSWithSuffix" ( dict "name" (include "newrelic.common.naming.fullname" .) "suffix" "userkey" ) -}} +{{- include "newrelic.common.userKey._customSecretName" . | default $default -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +*/}} +{{- define "newrelic.common.userKey.secretKeyName" -}} +{{- include "newrelic.common.userKey._customSecretKey" . | default "userKey" -}} +{{- end -}} + +{{/* +Return local API Key if set, global otherwise. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._userKey" -}} +{{- if .Values.userKey -}} + {{- .Values.userKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.userKey -}} + {{- .Values.global.userKey -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name of the secret holding the API Key. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretName" -}} +{{- if .Values.customUserKeySecretName -}} + {{- .Values.customUserKeySecretName -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretName -}} + {{- .Values.global.customUserKeySecretName -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the name key for the API Key inside the secret. +This helper is for internal use. +*/}} +{{- define "newrelic.common.userKey._customSecretKey" -}} +{{- if .Values.customUserKeySecretKey -}} + {{- .Values.customUserKeySecretKey -}} +{{- else if .Values.global -}} + {{- if .Values.global.customUserKeySecretKey }} + {{- .Values.global.customUserKeySecretKey -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey_secret.yaml.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey_secret.yaml.tpl new file mode 100644 index 000000000..b97985654 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_userkey_secret.yaml.tpl @@ -0,0 +1,21 @@ +{{/* +Renders the user key secret if user has not specified a custom secret. +*/}} +{{- define "newrelic.common.userKey.secret" }} +{{- if not (include "newrelic.common.userKey._customSecretName" .) }} +{{- /* Fail if user key is empty and required: */ -}} +{{- if not (include "newrelic.common.userKey._userKey" .) }} + {{- fail "You must specify a userKey or a customUserKeySecretName containing it" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic.common.userKey.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +data: + {{ include "newrelic.common.userKey.secretKeyName" . }}: {{ include "newrelic.common.userKey._userKey" . | b64enc }} +{{- end }} +{{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl new file mode 100644 index 000000000..2286d4681 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/templates/_verbose-log.tpl @@ -0,0 +1,54 @@ +{{- /* +Abstraction of the verbose toggle. +This helper allows to override the global `.global.verboseLog` with the value of `.verboseLog`. +Returns "true" if `verbose` is enabled, otherwise "" (empty string) +*/ -}} +{{- define "newrelic.common.verboseLog" -}} +{{- /* `get` will return "" (empty string) if value is not found, and the value otherwise, so we can type-assert with kindIs */ -}} +{{- if (get .Values "verboseLog" | kindIs "bool") -}} + {{- if .Values.verboseLog -}} + {{- /* + We want only to return when this is true, returning `false` here will template "false" (string) when doing + an `(include "newrelic.common.verboseLog" .)`, which is not an "empty string" so it is `true` if it is used + as an evaluation somewhere else. + */ -}} + {{- .Values.verboseLog -}} + {{- end -}} +{{- else -}} +{{- /* This allows us to use `$global` as an empty dict directly in case `Values.global` does not exists */ -}} +{{- $global := index .Values "global" | default dict -}} +{{- if get $global "verboseLog" | kindIs "bool" -}} + {{- if $global.verboseLog -}} + {{- $global.verboseLog -}} + {{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return true or false directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsBoolean" -}} +{{- if include "newrelic.common.verboseLog" . -}} +true +{{- else -}} +false +{{- end -}} +{{- end -}} + + + +{{- /* +Abstraction of the verbose toggle. +This helper abstracts the function "newrelic.common.verboseLog" to return 1 or 0 directly. +*/ -}} +{{- define "newrelic.common.verboseLog.valueAsInt" -}} +{{- if include "newrelic.common.verboseLog" . -}} +1 +{{- else -}} +0 +{{- end -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/values.yaml new file mode 100644 index 000000000..75e2d112a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/charts/common-library/values.yaml @@ -0,0 +1 @@ +# values are not needed for the library chart, however this file is still needed for helm lint to work. diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-lowdatamode-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-lowdatamode-values.yaml new file mode 100644 index 000000000..57b307a2d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-lowdatamode-values.yaml @@ -0,0 +1,9 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +lowDataMode: true + +image: + repository: e2e/nri-prometheus + tag: "test" # Defaults to chart's appVersion diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml new file mode 100644 index 000000000..7ff1a730f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-override-global-lowdatamode.yaml @@ -0,0 +1,10 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + lowDataMode: true + +lowDataMode: false + +image: + repository: e2e/nri-prometheus + tag: "test" # Defaults to chart's appVersion diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-values.yaml new file mode 100644 index 000000000..fcd07b2d3 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/ci/test-values.yaml @@ -0,0 +1,104 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +lowDataMode: true + +nameOverride: my-custom-name + +image: + registry: + repository: e2e/nri-prometheus + tag: "test" + imagePullPolicy: IfNotPresent + +resources: + limits: + cpu: 200m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + +rbac: + create: true + +serviceAccount: + # Specifies whether a ServiceAccount should be created + create: true + # The name of the ServiceAccount to use. + # If not set and create is true, a name is generated using the name template + name: "" + # Specify any annotations to add to the ServiceAccount + annotations: + foo: bar + +# If you wish to provide your own config.yaml file include it under config: +# the sample config file is included here as an example. +config: + scrape_duration: "60s" + scrape_timeout: "15s" + + scrape_services: false + scrape_endpoints: true + + audit: false + + insecure_skip_verify: false + + scrape_enabled_label: "prometheus.io/scrape" + + require_scrape_enabled_label_for_nodes: true + + transformations: + - description: "Custom transformation Example" + rename_attributes: + - metric_prefix: "foo_" + attributes: + old_label: "newLabel" + ignore_metrics: + - prefixes: + - bar_ + copy_attributes: + - from_metric: "foo_info" + to_metrics: "foo_" + match_by: + - namespace + +podAnnotations: + custom-pod-annotation: test + +podSecurityContext: + runAsUser: 1000 + runAsGroup: 3000 + fsGroup: 2000 + +containerSecurityContext: + runAsUser: 2000 + +tolerations: + - key: "key1" + operator: "Exists" + effect: "NoSchedule" + +nodeSelector: + kubernetes.io/os: linux + +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + +nrStaging: false + +fedramp: + enabled: true + +proxy: + +verboseLog: true diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/static/lowdatamodedefaults.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/static/lowdatamodedefaults.yaml new file mode 100644 index 000000000..f749e28da --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/static/lowdatamodedefaults.yaml @@ -0,0 +1,10 @@ +transformations: + - description: "Low data mode defaults" + ignore_metrics: + # Ignore the following metrics. + # These metrics are already collected by the New Relic Kubernetes Integration. + - prefixes: + - kube_ + - container_ + - machine_ + - cadvisor_ diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/_helpers.tpl b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/_helpers.tpl new file mode 100644 index 000000000..23c072bd7 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/_helpers.tpl @@ -0,0 +1,15 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Returns mergeTransformations +Helm can't merge maps of different types. Need to manually create a `transformations` section. +*/}} +{{- define "nri-prometheus.mergeTransformations" -}} + {{/* Remove current `transformations` from config. */}} + {{- omit .Values.config "transformations" | toYaml | nindent 4 -}} + {{/* Create new `transformations` yaml section with merged configs from .Values.config.transformations and lowDataMode. */}} + transformations: + {{- .Values.config.transformations | toYaml | nindent 4 -}} + {{ $lowDataDefault := .Files.Get "static/lowdatamodedefaults.yaml" | fromYaml }} + {{- $lowDataDefault.transformations | toYaml | nindent 4 -}} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrole.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrole.yaml new file mode 100644 index 000000000..ac4734d31 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrole.yaml @@ -0,0 +1,23 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: + - "nodes" + - "nodes/metrics" + - "nodes/stats" + - "nodes/proxy" + - "pods" + - "services" + - "endpoints" + verbs: ["get", "list", "watch"] +- nonResourceURLs: + - /metrics + verbs: + - get +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrolebinding.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrolebinding.yaml new file mode 100644 index 000000000..44244653f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "newrelic.common.naming.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/configmap.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/configmap.yaml new file mode 100644 index 000000000..5daeed64a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/configmap.yaml @@ -0,0 +1,21 @@ +kind: ConfigMap +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +apiVersion: v1 +data: + config.yaml: | + cluster_name: {{ include "newrelic.common.cluster" . }} +{{- if .Values.config -}} + {{- if and (.Values.config.transformations) (include "newrelic.common.lowDataMode" .) -}} + {{- include "nri-prometheus.mergeTransformations" . -}} + {{- else if (include "newrelic.common.lowDataMode" .) -}} + {{ $lowDataDefault := .Files.Get "static/lowdatamodedefaults.yaml" | fromYaml }} + {{- mergeOverwrite (deepCopy .Values.config) $lowDataDefault | toYaml | nindent 4 -}} + {{- else }} + {{- .Values.config | toYaml | nindent 4 -}} + {{- end -}} +{{- end -}} + diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/deployment.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/deployment.yaml new file mode 100644 index 000000000..8529b71f4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/deployment.yaml @@ -0,0 +1,98 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "newrelic.common.naming.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- /* We cannot use the common library here because of a legacy issue */}} + {{- /* `selector` is inmutable and the previous chart did not have all the idiomatic labels */}} + app.kubernetes.io/name: {{ include "newrelic.common.naming.name" . }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "newrelic.common.labels.podLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "newrelic.common.serviceAccount.name" . }} + {{- with include "newrelic.common.securityContext.pod" . }} + securityContext: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.images.renderPullSecrets" ( dict "pullSecrets" (list .Values.image.pullSecrets) "context" .) }} + imagePullSecrets: + {{- . | nindent 8 }} + {{- end }} + containers: + - name: nri-prometheus + {{- with include "newrelic.common.securityContext.container" . }} + securityContext: + {{- . | nindent 10 }} + {{- end }} + image: {{ include "newrelic.common.images.image" ( dict "imageRoot" .Values.image "context" .) }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - "--configfile=/etc/nri-prometheus/config.yaml" + ports: + - containerPort: 8080 + volumeMounts: + - name: config-volume + mountPath: /etc/nri-prometheus/ + env: + - name: "LICENSE_KEY" + valueFrom: + secretKeyRef: + name: {{ include "newrelic.common.license.secretName" . }} + key: {{ include "newrelic.common.license.secretKeyName" . }} + {{- if (include "newrelic.common.nrStaging" .) }} + - name: "METRIC_API_URL" + value: "https://staging-metric-api.newrelic.com/metric/v1/infra" + {{- else if (include "newrelic.common.fedramp.enabled" .) }} + - name: "METRIC_API_URL" + value: "https://gov-metric-api.newrelic.com/metric/v1" + {{- end }} + {{- with include "newrelic.common.proxy" . }} + - name: EMITTER_PROXY + value: {{ . | quote }} + {{- end }} + {{- with include "newrelic.common.verboseLog" . }} + - name: "VERBOSE" + value: {{ . | quote }} + {{- end }} + - name: "BEARER_TOKEN_FILE" + value: "/var/run/secrets/kubernetes.io/serviceaccount/token" + - name: "CA_FILE" + value: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + resources: + {{- toYaml .Values.resources | nindent 10 }} + volumes: + - name: config-volume + configMap: + name: {{ include "newrelic.common.naming.fullname" . }} + {{- with include "newrelic.common.priorityClassName" . }} + priorityClassName: {{ . }} + {{- end }} + {{- with include "newrelic.common.dnsConfig" . }} + dnsConfig: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.nodeSelector" . }} + nodeSelector: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.affinity" . }} + affinity: + {{- . | nindent 8 }} + {{- end }} + {{- with include "newrelic.common.tolerations" . }} + tolerations: + {{- . | nindent 8 }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/secret.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/secret.yaml new file mode 100644 index 000000000..f558ee86c --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/secret.yaml @@ -0,0 +1,2 @@ +{{- /* Common library will take care of creating the secret or not. */}} +{{- include "newrelic.common.license.secret" . }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/serviceaccount.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/serviceaccount.yaml new file mode 100644 index 000000000..df451ec90 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if (include "newrelic.common.serviceAccount.create" .) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "newrelic.common.serviceAccount.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "newrelic.common.labels" . | nindent 4 }} + {{- with (include "newrelic.common.serviceAccount.annotations" .) }} + annotations: + {{- . | nindent 4 }} + {{- end }} +{{- end -}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/configmap_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/configmap_test.yaml new file mode 100644 index 000000000..ae7d921fe --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/configmap_test.yaml @@ -0,0 +1,86 @@ +suite: test nri-prometheus configmap +templates: + - templates/configmap.yaml + - templates/deployment.yaml +tests: + - it: creates the config map with default config in values.yaml and cluster_name. + set: + licenseKey: fakeLicense + cluster: my-cluster-name + asserts: + - equal: + path: data["config.yaml"] + value: |- + cluster_name: my-cluster-name + audit: false + insecure_skip_verify: false + require_scrape_enabled_label_for_nodes: true + scrape_enabled_label: prometheus.io/scrape + scrape_endpoints: false + scrape_services: true + transformations: [] + template: templates/configmap.yaml + + - it: creates the config map with lowDataMode. + set: + licenseKey: fakeLicense + cluster: my-cluster-name + lowDataMode: true + asserts: + - equal: + path: data["config.yaml"] + value: |- + cluster_name: my-cluster-name + audit: false + insecure_skip_verify: false + require_scrape_enabled_label_for_nodes: true + scrape_enabled_label: prometheus.io/scrape + scrape_endpoints: false + scrape_services: true + transformations: + - description: Low data mode defaults + ignore_metrics: + - prefixes: + - kube_ + - container_ + - machine_ + - cadvisor_ + template: templates/configmap.yaml + + - it: merges existing transformation with lowDataMode. + set: + licenseKey: fakeLicense + cluster: my-cluster-name + lowDataMode: true + config: + transformations: + - description: Custom transformation Example + rename_attributes: + - metric_prefix: test_ + attributes: + container_name: containerName + asserts: + - equal: + path: data["config.yaml"] + value: |- + cluster_name: my-cluster-name + audit: false + insecure_skip_verify: false + require_scrape_enabled_label_for_nodes: true + scrape_enabled_label: prometheus.io/scrape + scrape_endpoints: false + scrape_services: true + transformations: + - description: Custom transformation Example + rename_attributes: + - attributes: + container_name: containerName + metric_prefix: test_ + - description: Low data mode defaults + ignore_metrics: + - prefixes: + - kube_ + - container_ + - machine_ + - cadvisor_ + template: templates/configmap.yaml diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/deployment_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/deployment_test.yaml new file mode 100644 index 000000000..cb6f90340 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/deployment_test.yaml @@ -0,0 +1,82 @@ +suite: test deployment +templates: + - templates/deployment.yaml + - templates/configmap.yaml + +release: + name: release + +tests: + - it: adds defaults. + set: + licenseKey: fakeLicense + cluster: test + asserts: + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/instance"] + value: release + template: templates/deployment.yaml + - equal: + path: spec.template.metadata.labels["app.kubernetes.io/name"] + value: nri-prometheus + template: templates/deployment.yaml + - equal: + path: spec.selector.matchLabels + value: + app.kubernetes.io/name: nri-prometheus + template: templates/deployment.yaml + - isNotEmpty: + path: spec.template.metadata.annotations["checksum/config"] + template: templates/deployment.yaml + + - it: adds METRIC_API_URL when nrStaging is true. + set: + licenseKey: fakeLicense + cluster: test + nrStaging: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: "METRIC_API_URL" + value: "https://staging-metric-api.newrelic.com/metric/v1/infra" + template: templates/deployment.yaml + + - it: adds FedRamp endpoint when FedRamp is enabled. + set: + licenseKey: fakeLicense + cluster: test + fedramp: + enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: "METRIC_API_URL" + value: "https://gov-metric-api.newrelic.com/metric/v1" + template: templates/deployment.yaml + + - it: adds proxy when enabled. + set: + licenseKey: fakeLicense + cluster: test + proxy: "https://my-proxy:9999" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: "EMITTER_PROXY" + value: "https://my-proxy:9999" + template: templates/deployment.yaml + + - it: set priorityClassName. + set: + licenseKey: fakeLicense + cluster: test + priorityClassName: foo + asserts: + - equal: + path: spec.template.spec.priorityClassName + value: foo + template: templates/deployment.yaml + diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/labels_test.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/labels_test.yaml new file mode 100644 index 000000000..2b6cb53bb --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/tests/labels_test.yaml @@ -0,0 +1,32 @@ +suite: test object names +templates: + - templates/clusterrole.yaml + - templates/clusterrolebinding.yaml + - templates/configmap.yaml + - templates/deployment.yaml + - templates/secret.yaml + - templates/serviceaccount.yaml + +release: + name: release + revision: + +tests: + - it: adds default labels. + set: + licenseKey: fakeLicense + cluster: test + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/instance"] + value: release + - equal: + path: metadata.labels["app.kubernetes.io/managed-by"] + value: Helm + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: nri-prometheus + - isNotEmpty: + path: metadata.labels["app.kubernetes.io/version"] + - isNotEmpty: + path: metadata.labels["helm.sh/chart"] diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/values.yaml new file mode 100644 index 000000000..4c562cc66 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/nri-prometheus/values.yaml @@ -0,0 +1,251 @@ +# -- Override the name of the chart +nameOverride: "" +# -- Override the full name of the release +fullnameOverride: "" + +# -- Name of the Kubernetes cluster monitored. Can be configured also with `global.cluster` +cluster: "" +# -- This set this license key to use. Can be configured also with `global.licenseKey` +licenseKey: "" +# -- In case you don't want to have the license key in you values, this allows you to point to a user created secret to get the key from there. Can be configured also with `global.customSecretName` +customSecretName: "" +# -- In case you don't want to have the license key in you values, this allows you to point to which secret key is the license key located. Can be configured also with `global.customSecretLicenseKey` +customSecretLicenseKey: "" + +# -- Image for the New Relic Kubernetes integration +# @default -- See `values.yaml` +image: + registry: + repository: newrelic/nri-prometheus + tag: "" # Defaults to chart's appVersion + imagePullPolicy: IfNotPresent + # -- The secrets that are needed to pull images from a custom registry. + pullSecrets: [] + # - name: regsecret + +resources: {} + # limits: + # cpu: 200m + # memory: 512Mi + # requests: + # cpu: 100m + # memory: 256Mi + +rbac: + # -- Specifies whether RBAC resources should be created + create: true + +serviceAccount: + # -- Add these annotations to the service account we create. Can be configured also with `global.serviceAccount.annotations` + annotations: {} + # -- Configures if the service account should be created or not. Can be configured also with `global.serviceAccount.create` + create: true + # -- Change the name of the service account. This is honored if you disable on this cahrt the creation of the service account so you can use your own. Can be configured also with `global.serviceAccount.name` + name: + +# -- Annotations to be added to all pods created by the integration. +podAnnotations: {} +# -- Additional labels for chart pods. Can be configured also with `global.podLabels` +podLabels: {} +# -- Additional labels for chart objects. Can be configured also with `global.labels` +labels: {} + +# -- Sets pod's priorityClassName. Can be configured also with `global.priorityClassName` +priorityClassName: "" +# -- (bool) Sets pod's hostNetwork. Can be configured also with `global.hostNetwork` +# @default -- `false` +hostNetwork: +# -- Sets pod's dnsConfig. Can be configured also with `global.dnsConfig` +dnsConfig: {} + +# -- Sets security context (at pod level). Can be configured also with `global.podSecurityContext` +podSecurityContext: {} +# -- Sets security context (at container level). Can be configured also with `global.containerSecurityContext` +containerSecurityContext: {} + +# -- Sets pod/node affinities. Can be configured also with `global.affinity` +affinity: {} +# -- Sets pod's node selector. Can be configured also with `global.nodeSelector` +nodeSelector: {} +# -- Sets pod's tolerations to node taints. Can be configured also with `global.tolerations` +tolerations: [] + + +# -- Provides your own `config.yaml` for this integration. +# Ref: https://docs.newrelic.com/docs/infrastructure/prometheus-integrations/install-configure-openmetrics/configure-prometheus-openmetrics-integrations/#example-configuration-file +# @default -- See `values.yaml` +config: + # How often the integration should run. + # Default: "30s" + # scrape_duration: "30s" + + # The HTTP client timeout when fetching data from targets. + # Default: "5s" + # scrape_timeout: "5s" + + # scrape_services Allows to enable scraping the service and not the endpoints behind. + # When endpoints are scraped this is no longer needed + scrape_services: true + + # scrape_endpoints Allows to enable scraping directly endpoints instead of services as prometheus service natively does. + # Please notice that depending on the number of endpoints behind a service the load can increase considerably + scrape_endpoints: false + + # How old must the entries used for calculating the counters delta be + # before the telemetry emitter expires them. + # Default: "5m" + # telemetry_emitter_delta_expiration_age: "5m" + + # How often must the telemetry emitter check for expired delta entries. + # Default: "5m" + # telemetry_emitter_delta_expiration_check_interval: "5m" + + # Whether the integration should run in audit mode or not. Defaults to false. + # Audit mode logs the uncompressed data sent to New Relic. Use this to log all data sent. + # It does not include verbose mode. This can lead to a high log volume, use with care. + # Default: false + audit: false + + # Whether the integration should skip TLS verification or not. + # Default: false + insecure_skip_verify: false + + # The label used to identify scrapeable targets. + # Targets can be identified using a label or annotation. + # Default: "prometheus.io/scrape" + scrape_enabled_label: "prometheus.io/scrape" + + # Whether k8s nodes need to be labelled to be scraped or not. + # Default: true + require_scrape_enabled_label_for_nodes: true + + # Number of worker threads used for scraping targets. + # For large clusters with many (>400) targets, slowly increase until scrape + # time falls between the desired `scrape_duration`. + # Increasing this value too much will result in huge memory consumption if too + # many metrics are being scraped. + # Default: 4 + # worker_threads: 4 + + # Maximum number of metrics to keep in memory until a report is triggered. + # Changing this value is not recommended unless instructed by the New Relic support team. + # max_stored_metrics: 10000 + + # Minimum amount of time to wait between reports. Cannot be lowered than the default, 200ms. + # Changing this value is not recommended unless instructed by the New Relic support team. + # min_emitter_harvest_period: 200ms + + # targets: + # - description: Secure etcd example + # urls: ["https://192.168.3.1:2379", "https://192.168.3.2:2379", "https://192.168.3.3:2379"] + # If true the Kubernetes Service Account token will be included as a Bearer token in the HTTP request. + # use_bearer: false + # tls_config: + # ca_file_path: "/etc/etcd/etcd-client-ca.crt" + # cert_file_path: "/etc/etcd/etcd-client.crt" + # key_file_path: "/etc/etcd/etcd-client.key" + + # Certificate to add to the root CA that the emitter will use when + # verifying server certificates. + # If left empty, TLS uses the host's root CA set. + # emitter_ca_file: "/path/to/cert/server.pem" + + # Set to true in order to stop autodiscovery in the k8s cluster. It can be useful when running the Pod with a service account + # having limited privileges. + # Default: false + # disable_autodiscovery: false + + # Whether the emitter should skip TLS verification when submitting data. + # Default: false + # emitter_insecure_skip_verify: false + + # Histogram support is based on New Relic's guidelines for higher + # level metrics abstractions https://github.com/newrelic/newrelic-exporter-specs/blob/master/Guidelines.md. + # To better support visualization of this data, percentiles are calculated + # based on the histogram metrics and sent to New Relic. + # By default, the following percentiles are calculated: 50, 95 and 99. + # + # percentiles: + # - 50 + # - 95 + # - 99 + + transformations: [] + # - description: "Custom transformation Example" + # rename_attributes: + # - metric_prefix: "" + # attributes: + # container_name: "containerName" + # pod_name: "podName" + # namespace: "namespaceName" + # node: "nodeName" + # container: "containerName" + # pod: "podName" + # deployment: "deploymentName" + # ignore_metrics: + # # Ignore the following metrics. + # # These metrics are already collected by the New Relic Kubernetes Integration. + # - prefixes: + # - kube_daemonset_ + # - kube_deployment_ + # - kube_endpoint_ + # - kube_namespace_ + # - kube_node_ + # - kube_persistentvolume_ + # - kube_pod_ + # - kube_replicaset_ + # - kube_service_ + # - kube_statefulset_ + # copy_attributes: + # # Copy all the labels from the timeseries with metric name + # # `kube_hpa_labels` into every timeseries with a metric name that + # # starts with `kube_hpa_` only if they share the same `namespace` + # # and `hpa` labels. + # - from_metric: "kube_hpa_labels" + # to_metrics: "kube_hpa_" + # match_by: + # - namespace + # - hpa + # - from_metric: "kube_daemonset_labels" + # to_metrics: "kube_daemonset_" + # match_by: + # - namespace + # - daemonset + # - from_metric: "kube_statefulset_labels" + # to_metrics: "kube_statefulset_" + # match_by: + # - namespace + # - statefulset + # - from_metric: "kube_endpoint_labels" + # to_metrics: "kube_endpoint_" + # match_by: + # - namespace + # - endpoint + # - from_metric: "kube_service_labels" + # to_metrics: "kube_service_" + # match_by: + # - namespace + # - service + # - from_metric: "kube_node_labels" + # to_metrics: "kube_node_" + # match_by: + # - namespace + # - node + +# -- (bool) Reduces number of metrics sent in order to reduce costs. Can be configured also with `global.lowDataMode` +# @default -- false +lowDataMode: + +# -- Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port`. Can be configured also with `global.proxy` +proxy: "" + +# -- (bool) Send the metrics to the staging backend. Requires a valid staging license key. Can be configured also with `global.nrStaging` +# @default -- false +nrStaging: +fedramp: + # fedramp.enabled -- (bool) Enables FedRAMP. Can be configured also with `global.fedramp.enabled` + # @default -- false + enabled: +# -- (bool) Sets the debug logs to this integration or all integrations if it is set globally. Can be configured also with `global.verboseLog` +# @default -- false +verboseLog: diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/Chart.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/Chart.yaml new file mode 100644 index 000000000..a0ce0a388 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: pixie-operator-chart +type: application +version: 0.1.6 diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/olm_crd.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/olm_crd.yaml new file mode 100644 index 000000000..3f5429f78 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/olm_crd.yaml @@ -0,0 +1,9045 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: catalogsources.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: CatalogSource + listKind: CatalogSourceList + plural: catalogsources + shortNames: + - catsrc + singular: catalogsource + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The pretty name of the catalog + jsonPath: .spec.displayName + name: Display + type: string + - description: The type of the catalog + jsonPath: .spec.sourceType + name: Type + type: string + - description: The publisher of the catalog + jsonPath: .spec.publisher + name: Publisher + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: CatalogSource is a repository of CSVs, CRDs, and operator packages. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + required: + - sourceType + properties: + address: + description: 'Address is a host that OLM can use to connect to a pre-existing registry. Format: : Only used when SourceType = SourceTypeGrpc. Ignored when the Image field is set.' + type: string + configMap: + description: ConfigMap is the name of the ConfigMap to be used to back a configmap-server registry. Only used when SourceType = SourceTypeConfigmap or SourceTypeInternal. + type: string + description: + type: string + displayName: + description: Metadata + type: string + grpcPodConfig: + description: GrpcPodConfig exposes different overrides for the pod spec of the CatalogSource Pod. Only used when SourceType = SourceTypeGrpc and Image is set. + type: object + properties: + affinity: + description: Affinity is the catalog source's pod's affinity. + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + extractContent: + description: ExtractContent configures the gRPC catalog Pod to extract catalog metadata from the provided index image and use a well-known version of the `opm` server to expose it. The catalog index image that this CatalogSource is configured to use *must* be using the file-based catalogs in order to utilize this feature. + type: object + required: + - cacheDir + - catalogDir + properties: + cacheDir: + description: CacheDir is the directory storing the pre-calculated API cache. + type: string + catalogDir: + description: CatalogDir is the directory storing the file-based catalog contents. + type: string + memoryTarget: + description: "MemoryTarget configures the $GOMEMLIMIT value for the gRPC catalog Pod. This is a soft memory limit for the server, which the runtime will attempt to meet but makes no guarantees that it will do so. If this value is set, the Pod will have the following modifications made to the container running the server: - the $GOMEMLIMIT environment variable will be set to this value in bytes - the memory request will be set to this value \n This field should be set if it's desired to reduce the footprint of a catalog server as much as possible, or if a catalog being served is very large and needs more than the default allocation. If your index image has a file- system cache, determine a good approximation for this value by doubling the size of the package cache at /tmp/cache/cache/packages.json in the index image. \n This field is best-effort; if unset, no default will be used and no Pod memory limit or $GOMEMLIMIT value will be set." + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + nodeSelector: + description: NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node's labels for the pod to be scheduled on that node. + type: object + additionalProperties: + type: string + priorityClassName: + description: If specified, indicates the pod's priority. If not specified, the pod priority will be default or zero if there is no default. + type: string + securityContextConfig: + description: "SecurityContextConfig can be one of `legacy` or `restricted`. The CatalogSource's pod is either injected with the right pod.spec.securityContext and pod.spec.container[*].securityContext values to allow the pod to run in Pod Security Admission (PSA) `restricted` mode, or doesn't set these values at all, in which case the pod can only be run in PSA `baseline` or `privileged` namespaces. Currently if the SecurityContextConfig is unspecified, the default value of `legacy` is used. Specifying a value other than `legacy` or `restricted` result in a validation error. When using older catalog images, which could not be run in `restricted` mode, the SecurityContextConfig should be set to `legacy`. \n In a future version will the default will be set to `restricted`, catalog maintainers should rebuild their catalogs with a version of opm that supports running catalogSource pods in `restricted` mode to prepare for these changes. \n More information about PSA can be found here: https://kubernetes.io/docs/concepts/security/pod-security-admission/'" + type: string + default: legacy + enum: + - legacy + - restricted + tolerations: + description: Tolerations are the catalog source's pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + icon: + type: object + required: + - base64data + - mediatype + properties: + base64data: + type: string + mediatype: + type: string + image: + description: Image is an operator-registry container image to instantiate a registry-server with. Only used when SourceType = SourceTypeGrpc. If present, the address field is ignored. + type: string + priority: + description: 'Priority field assigns a weight to the catalog source to prioritize them so that it can be consumed by the dependency resolver. Usage: Higher weight indicates that this catalog source is preferred over lower weighted catalog sources during dependency resolution. The range of the priority value can go from positive to negative in the range of int32. The default value to a catalog source with unassigned priority would be 0. The catalog source with the same priority values will be ranked lexicographically based on its name.' + type: integer + publisher: + type: string + secrets: + description: Secrets represent set of secrets that can be used to access the contents of the catalog. It is best to keep this list small, since each will need to be tried for every catalog entry. + type: array + items: + type: string + sourceType: + description: SourceType is the type of source + type: string + updateStrategy: + description: UpdateStrategy defines how updated catalog source images can be discovered Consists of an interval that defines polling duration and an embedded strategy type + type: object + properties: + registryPoll: + type: object + properties: + interval: + description: Interval is used to determine the time interval between checks of the latest catalog source version. The catalog operator polls to see if a new version of the catalog source is available. If available, the latest image is pulled and gRPC traffic is directed to the latest catalog source. + type: string + status: + type: object + properties: + conditions: + description: Represents the state of a CatalogSource. Note that Message and Reason represent the original status information, which may be migrated to be conditions based in the future. Any new features introduced will use conditions. + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configMapReference: + type: object + required: + - name + - namespace + properties: + lastUpdateTime: + type: string + format: date-time + name: + type: string + namespace: + type: string + resourceVersion: + type: string + uid: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + connectionState: + type: object + required: + - lastObservedState + properties: + address: + type: string + lastConnect: + type: string + format: date-time + lastObservedState: + type: string + latestImageRegistryPoll: + description: The last time the CatalogSource image registry has been polled to ensure the image is up-to-date + type: string + format: date-time + message: + description: A human readable message indicating details about why the CatalogSource is in this condition. + type: string + reason: + description: Reason is the reason the CatalogSource was transitioned to its current state. + type: string + registryService: + type: object + properties: + createdAt: + type: string + format: date-time + port: + type: string + protocol: + type: string + serviceName: + type: string + serviceNamespace: + type: string + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: clusterserviceversions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: ClusterServiceVersion + listKind: ClusterServiceVersionList + plural: clusterserviceversions + shortNames: + - csv + - csvs + singular: clusterserviceversion + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The name of the CSV + jsonPath: .spec.displayName + name: Display + type: string + - description: The version of the CSV + jsonPath: .spec.version + name: Version + type: string + - description: The name of a CSV that this one replaces + jsonPath: .spec.replaces + name: Replaces + type: string + - jsonPath: .status.phase + name: Phase + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterServiceVersion is a Custom Resource of type `ClusterServiceVersionSpec`. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ClusterServiceVersionSpec declarations tell OLM how to install an operator that can manage apps for a given version. + type: object + required: + - displayName + - install + properties: + annotations: + description: Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. + type: object + additionalProperties: + type: string + apiservicedefinitions: + description: APIServiceDefinitions declares all of the extension apis managed or required by an operator being ran by ClusterServiceVersion. + type: object + properties: + owned: + type: array + items: + description: APIServiceDescription provides details to OLM about apis provided via aggregation + type: object + required: + - group + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + deploymentName: + type: string + description: + type: string + displayName: + type: string + group: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a reference to a Kubernetes resource type that the referrer utilizes. + type: object + required: + - kind + - name + - version + properties: + kind: + description: Kind of the referenced resource type. + type: string + name: + description: Plural name of the referenced resource type (CustomResourceDefinition.Spec.Names[].Plural). Empty string if the referenced resource type is not a custom resource. + type: string + version: + description: API Version of the referenced resource type. + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + required: + type: array + items: + description: APIServiceDescription provides details to OLM about apis provided via aggregation + type: object + required: + - group + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + deploymentName: + type: string + description: + type: string + displayName: + type: string + group: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a reference to a Kubernetes resource type that the referrer utilizes. + type: object + required: + - kind + - name + - version + properties: + kind: + description: Kind of the referenced resource type. + type: string + name: + description: Plural name of the referenced resource type (CustomResourceDefinition.Spec.Names[].Plural). Empty string if the referenced resource type is not a custom resource. + type: string + version: + description: API Version of the referenced resource type. + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + cleanup: + description: Cleanup specifies the cleanup behaviour when the CSV gets deleted + type: object + required: + - enabled + properties: + enabled: + type: boolean + customresourcedefinitions: + description: "CustomResourceDefinitions declares all of the CRDs managed or required by an operator being ran by ClusterServiceVersion. \n If the CRD is present in the Owned list, it is implicitly required." + type: object + properties: + owned: + type: array + items: + description: CRDDescription provides details to OLM about the CRDs + type: object + required: + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + description: + type: string + displayName: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a reference to a Kubernetes resource type that the referrer utilizes. + type: object + required: + - kind + - name + - version + properties: + kind: + description: Kind of the referenced resource type. + type: string + name: + description: Plural name of the referenced resource type (CustomResourceDefinition.Spec.Names[].Plural). Empty string if the referenced resource type is not a custom resource. + type: string + version: + description: API Version of the referenced resource type. + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + required: + type: array + items: + description: CRDDescription provides details to OLM about the CRDs + type: object + required: + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + description: + type: string + displayName: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a reference to a Kubernetes resource type that the referrer utilizes. + type: object + required: + - kind + - name + - version + properties: + kind: + description: Kind of the referenced resource type. + type: string + name: + description: Plural name of the referenced resource type (CustomResourceDefinition.Spec.Names[].Plural). Empty string if the referenced resource type is not a custom resource. + type: string + version: + description: API Version of the referenced resource type. + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + description: + description: Description of the operator. Can include the features, limitations or use-cases of the operator. + type: string + displayName: + description: The name of the operator in display format. + type: string + icon: + description: The icon for this operator. + type: array + items: + type: object + required: + - base64data + - mediatype + properties: + base64data: + type: string + mediatype: + type: string + install: + description: NamedInstallStrategy represents the block of an ClusterServiceVersion resource where the install strategy is specified. + type: object + required: + - strategy + properties: + spec: + description: StrategyDetailsDeployment represents the parsed details of a Deployment InstallStrategy. + type: object + required: + - deployments + properties: + clusterPermissions: + type: array + items: + description: StrategyDeploymentPermissions describe the rbac rules and service account needed by the install strategy + type: object + required: + - rules + - serviceAccountName + properties: + rules: + type: array + items: + description: PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to. + type: object + required: + - verbs + properties: + apiGroups: + description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. + type: array + items: + type: string + nonResourceURLs: + description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + type: array + items: + type: string + resourceNames: + description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + type: array + items: + type: string + resources: + description: Resources is a list of resources this rule applies to. '*' represents all resources. + type: array + items: + type: string + verbs: + description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. + type: array + items: + type: string + serviceAccountName: + type: string + deployments: + type: array + items: + description: StrategyDeploymentSpec contains the name, spec and labels for the deployment ALM should create + type: object + required: + - name + - spec + properties: + label: + description: Set is a map of label:value. It implements Labels. + type: object + additionalProperties: + type: string + name: + type: string + spec: + description: DeploymentSpec is the specification of the desired behavior of the Deployment. + type: object + required: + - selector + - template + properties: + minReadySeconds: + description: Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready) + type: integer + format: int32 + paused: + description: Indicates that the deployment is paused. + type: boolean + progressDeadlineSeconds: + description: The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s. + type: integer + format: int32 + replicas: + description: Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1. + type: integer + format: int32 + revisionHistoryLimit: + description: The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10. + type: integer + format: int32 + selector: + description: Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + strategy: + description: The deployment strategy to use to replace existing pods with new ones. + type: object + properties: + rollingUpdate: + description: 'Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate. --- TODO: Update this to follow our convention for oneOf, whatever we decide it to be.' + type: object + properties: + maxSurge: + description: 'The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.' + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + description: 'The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.' + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: + description: Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate. + type: string + template: + description: Template describes the pods that will be created. The only allowed template.spec.restartPolicy value is "Always". + type: object + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + type: object + x-kubernetes-preserve-unknown-fields: true + spec: + description: 'Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + type: object + required: + - containers + properties: + activeDeadlineSeconds: + description: Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. + type: integer + format: int64 + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containers: + description: List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The container image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The container image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod''s termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Modifying this array with strategic merge patch may corrupt the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resizePolicy: + description: Resources resize policy for the container. + type: array + items: + description: ContainerResizePolicy represents resource resize policy for the container. + type: object + required: + - resourceName + - restartPolicy + properties: + resourceName: + description: 'Name of the resource to which this resource resize policy applies. Supported values: cpu, memory.' + type: string + restartPolicy: + description: Restart policy to apply when specified resource is resized. If not specified, it defaults to NotRequired. + type: string + x-kubernetes-list-type: atomic + resources: + description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + restartPolicy: + description: 'RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is "Always". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod''s restart policy and the container type. Setting the RestartPolicy as "Always" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" will be shut down. This lifecycle differs from normal init containers and is often referred to as a "sidecar" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.' + type: string + securityContext: + description: 'SecurityContext defines the security options the container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + dnsConfig: + description: Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. + type: object + properties: + nameservers: + description: A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed. + type: array + items: + type: string + options: + description: A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy. + type: array + items: + description: PodDNSConfigOption defines DNS resolver options of a pod. + type: object + properties: + name: + description: Required. + type: string + value: + type: string + searches: + description: A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed. + type: array + items: + type: string + dnsPolicy: + description: Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + type: string + enableServiceLinks: + description: 'EnableServiceLinks indicates whether information about services should be injected into pod''s environment variables, matching the syntax of Docker links. Optional: Defaults to true.' + type: boolean + ephemeralContainers: + description: List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. + type: array + items: + description: "An EphemeralContainer is a temporary container that you may add to an existing Pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a Pod is removed or restarted. The kubelet may evict a Pod if an ephemeral container causes the Pod to exceed its resource allocation. \n To add an ephemeral container, use the ephemeralcontainers subresource of an existing Pod. Ephemeral containers may not be removed or restarted." + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Lifecycle is not allowed for ephemeral containers. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod''s termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers. + type: string + ports: + description: Ports are not allowed for ephemeral containers. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resizePolicy: + description: Resources resize policy for the container. + type: array + items: + description: ContainerResizePolicy represents resource resize policy for the container. + type: object + required: + - resourceName + - restartPolicy + properties: + resourceName: + description: 'Name of the resource to which this resource resize policy applies. Supported values: cpu, memory.' + type: string + restartPolicy: + description: Restart policy to apply when specified resource is resized. If not specified, it defaults to NotRequired. + type: string + x-kubernetes-list-type: atomic + resources: + description: Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod. + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + restartPolicy: + description: Restart policy for the container to manage the restart behavior of each container within a pod. This may only be set for init containers. You cannot set this field on ephemeral containers. + type: string + securityContext: + description: 'Optional: SecurityContext defines the security options the ephemeral container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.' + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + targetContainerName: + description: "If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container uses the namespaces configured in the Pod spec. \n The container runtime must implement support for this feature. If the runtime does not support namespace targeting then the result of setting this field is undefined." + type: string + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file. + type: object + properties: + hostnames: + description: Hostnames for the above IP address. + type: array + items: + type: string + ip: + description: IP address of the host file entry. + type: string + hostIPC: + description: 'Use the host''s ipc namespace. Optional: Default to false.' + type: boolean + hostNetwork: + description: Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false. + type: boolean + hostPID: + description: 'Use the host''s pid namespace. Optional: Default to false.' + type: boolean + hostUsers: + description: 'Use the host''s user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.' + type: boolean + hostname: + description: Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value. + type: string + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + type: array + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + initContainers: + description: 'List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' + type: array + items: + description: A single application container that you want to run within a pod. + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The container image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The container image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Container image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod''s termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for the backward compatibility. There are no validation of this field and lifecycle hooks will fail in runtime when tcp handler is specified. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Modifying this array with strategic merge patch may corrupt the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resizePolicy: + description: Resources resize policy for the container. + type: array + items: + description: ContainerResizePolicy represents resource resize policy for the container. + type: object + required: + - resourceName + - restartPolicy + properties: + resourceName: + description: 'Name of the resource to which this resource resize policy applies. Supported values: cpu, memory.' + type: string + restartPolicy: + description: Restart policy to apply when specified resource is resized. If not specified, it defaults to NotRequired. + type: string + x-kubernetes-list-type: atomic + resources: + description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + restartPolicy: + description: 'RestartPolicy defines the restart behavior of individual containers in a pod. This field may only be set for init containers, and the only allowed value is "Always". For non-init containers or when this field is not specified, the restart behavior is defined by the Pod''s restart policy and the container type. Setting the RestartPolicy as "Always" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy "Always" will be shut down. This lifecycle differs from normal init containers and is often referred to as a "sidecar" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.' + type: string + securityContext: + description: 'SecurityContext defines the security options the container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + grpc: + description: GRPC specifies an action involving a GRPC port. + type: object + required: + - port + properties: + port: + description: Port number of the gRPC service. Number must be in the range 1 to 65535. + type: integer + format: int32 + service: + description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). \n If this is not specified, the default behavior is defined by gRPC." + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: TCPSocket specifies an action involving a TCP port. + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + type: integer + format: int64 + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + nodeName: + description: NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + x-kubernetes-map-type: atomic + os: + description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set. \n If the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions \n If the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup" + type: object + required: + - name + properties: + name: + description: 'Name is the name of the operating system. The currently supported values are linux and windows. Additional value may be defined in future and can be one of: https://github.com/opencontainers/runtime-spec/blob/master/config.md#platform-specific-configuration Clients should expect to handle additional values and treat unrecognized values in this field as os: null' + type: string + overhead: + description: 'Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preemptionPolicy: + description: PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. + type: string + priority: + description: The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. + type: integer + format: int32 + priorityClassName: + description: If specified, indicates the pod's priority. "system-node-critical" and "system-cluster-critical" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default. + type: string + readinessGates: + description: 'If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to "True" More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates' + type: array + items: + description: PodReadinessGate contains the reference to a pod condition + type: object + required: + - conditionType + properties: + conditionType: + description: ConditionType refers to a condition in the pod's condition list with matching type. + type: string + resourceClaims: + description: "ResourceClaims defines which ResourceClaims must be allocated and reserved before the Pod is allowed to start. The resources will be made available to those containers which consume them by name. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable." + type: array + items: + description: PodResourceClaim references exactly one ResourceClaim through a ClaimSource. It adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name. + type: object + required: + - name + properties: + name: + description: Name uniquely identifies this resource claim inside the pod. This must be a DNS_LABEL. + type: string + source: + description: Source describes where to find the ResourceClaim. + type: object + properties: + resourceClaimName: + description: ResourceClaimName is the name of a ResourceClaim object in the same namespace as this pod. + type: string + resourceClaimTemplateName: + description: "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod. \n The template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses. \n This field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim." + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + restartPolicy: + description: 'Restart policy for all containers within the pod. One of Always, OnFailure, Never. In some contexts, only a subset of those values may be permitted. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy' + type: string + runtimeClassName: + description: 'RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class' + type: string + schedulerName: + description: If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler. + type: string + schedulingGates: + description: "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod. \n SchedulingGates can only be set at pod creation time, and be removed only afterwards. \n This is a beta feature enabled by the PodSchedulingReadiness feature gate." + type: array + items: + description: PodSchedulingGate is associated to a Pod to guard its scheduling. + type: object + required: + - name + properties: + name: + description: Name of the scheduling gate. Each scheduling gate must have a unique name field. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + securityContext: + description: 'SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.' + type: object + properties: + fsGroup: + description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: \n 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- \n If unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows." + type: integer + format: int64 + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. Note that this field cannot be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + supplementalGroups: + description: A list of groups applied to the first process run in each container, in addition to the container's primary GID, the fsGroup (if specified), and group memberships defined in the container image for the uid of the container process. If unspecified, no additional groups are added to any container. Note that group memberships defined in the container image for the uid of the container process are still effective, even if they are not included in this list. Note that this field cannot be set when spec.os.name is windows. + type: array + items: + type: integer + format: int64 + sysctls: + description: Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. Note that this field cannot be set when spec.os.name is windows. + type: array + items: + description: Sysctl defines a kernel parameter to be set + type: object + required: + - name + - value + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + serviceAccount: + description: 'DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.' + type: string + serviceAccountName: + description: 'ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/' + type: string + setHostnameAsFQDN: + description: If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false. + type: boolean + shareProcessNamespace: + description: 'Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.' + type: boolean + subdomain: + description: If specified, the fully qualified Pod hostname will be "...svc.". If not specified, the pod will not have a domainname at all. + type: string + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds. + type: integer + format: int64 + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + topologySpreadConstraints: + description: TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed. + type: array + items: + description: TopologySpreadConstraint specifies how to spread matching pods among the given topology. + type: object + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + properties: + labelSelector: + description: LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + matchLabelKeys: + description: "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot be set when LabelSelector isn't set. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector. \n This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default)." + type: array + items: + type: string + x-kubernetes-list-type: atomic + maxSkew: + description: 'MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It''s a required field. Default value is 1 and 0 is not allowed.' + type: integer + format: int32 + minDomains: + description: "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule. \n For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: | zone1 | zone2 | zone3 | | P P | P P | P P | The number of domains is less than 5(MinDomains), so \"global minimum\" is treated as 0. In this situation, new pod with the same labelSelector cannot be scheduled, because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, it will violate MaxSkew. \n This is a beta field and requires the MinDomainsInPodTopologySpread feature gate to be enabled (enabled by default)." + type: integer + format: int32 + nodeAffinityPolicy: + description: "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. \n If this value is nil, the behavior is equivalent to the Honor policy. This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag." + type: string + nodeTaintsPolicy: + description: "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included. \n If this value is nil, the behavior is equivalent to the Ignore policy. This is a beta-level feature default enabled by the NodeInclusionPolicyInPodTopologySpread feature flag." + type: string + topologyKey: + description: TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a "bucket", and try to put balanced number of pods into each bucket. We define a domain as a particular instance of a topology. Also, we define an eligible domain as a domain whose nodes meet the requirements of nodeAffinityPolicy and nodeTaintsPolicy. e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it. - ScheduleAnyway tells the scheduler to schedule the pod in any location, but giving higher precedence to topologies that would help reduce the skew. A constraint is considered "Unsatisfiable" for an incoming pod if and only if every possible node assignment for that pod would violate "MaxSkew" on some topology. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won''t make it *more* imbalanced. It''s a required field.' + type: string + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + description: 'List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + awsElasticBlockStore: + description: 'awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: object + required: + - volumeID + properties: + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).' + type: integer + format: int32 + readOnly: + description: 'readOnly value true will force the readOnly setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + azureDisk: + description: azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + type: object + required: + - diskName + - diskURI + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the blob storage + type: string + fsType: + description: fsType is Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared' + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + azureFile: + description: azureFile represents an Azure File Service mount on the host and bind mount to the pod. + type: object + required: + - secretName + - shareName + properties: + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + cephfs: + description: cephFS represents a Ceph FS mount on the host that shares a pod's lifetime + type: object + required: + - monitors + properties: + monitors: + description: 'monitors is Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: array + items: + type: string + path: + description: 'path is Optional: Used as the mounted root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'user is optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + cinder: + description: 'cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: object + required: + - volumeID + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'secretRef is optional: points to a secret object containing parameters used to connect to OpenStack.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeID: + description: 'volumeID used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: 'defaultMode is optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + csi: + description: csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). + type: object + required: + - driver + properties: + driver: + description: driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: fsType to mount. Ex. "ext4", "xfs", "ntfs". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: nodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + readOnly: + description: readOnly specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + description: volumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values. + type: object + additionalProperties: + type: string + downwardAPI: + description: downwardAPI represents downward API about the pod that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits to use on created files by default. Must be a Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: Items is a list of downward API volume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + emptyDir: + description: 'emptyDir represents a temporary directory that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: object + properties: + medium: + description: 'medium represents what type of storage medium should back this directory. The default is "" which means to use the node''s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + description: 'sizeLimit is the total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + ephemeral: + description: "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed. \n Use this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity tracking are needed, c) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource for more information on the connection between this volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod. \n Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information. \n A pod can use both types of ephemeral volumes and persistent volumes at the same time." + type: object + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long). \n An existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster. \n This field is read-only and no changes will be made by Kubernetes to the PVC after it has been created. \n Required, must not be nil." + type: object + required: + - spec + properties: + metadata: + description: May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here. + type: object + properties: + accessModes: + description: 'accessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + type: array + items: + type: string + dataSource: + description: 'dataSource field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source. When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. If the namespace is specified, then dataSourceRef will not be copied to dataSource.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + dataSourceRef: + description: 'dataSourceRef specifies the object from which to populate the volume with data, if a non-empty volume is desired. This may be any object from a non-empty API group (non core object) or a PersistentVolumeClaim object. When this field is specified, volume binding will only succeed if the type of the specified object matches some installed volume populator or dynamic provisioner. This field will replace the functionality of the dataSource field and as such if both fields are non-empty, they must have the same value. For backwards compatibility, when namespace isn''t specified in dataSourceRef, both fields (dataSource and dataSourceRef) will be set to the same value automatically if one of them is empty and the other is non-empty. When namespace is specified in dataSourceRef, dataSource isn''t set to the same value and must be empty. There are three important differences between dataSource and dataSourceRef: * While dataSource only allows two specific types of objects, dataSourceRef allows any non-core object, as well as PersistentVolumeClaim objects. * While dataSource ignores disallowed values (dropping them), dataSourceRef preserves all values, and generates an error if a disallowed value is specified. * While dataSource only allows local objects, dataSourceRef allows objects in any namespaces. (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + namespace: + description: Namespace is the namespace of resource being referenced Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + resources: + description: 'resources represents the minimum resources the volume should have. If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: selector is a label query over volumes to consider for binding. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + storageClassName: + description: 'storageClassName is the name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the PersistentVolume backing this claim. + type: string + fc: + description: fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + type: object + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + lun: + description: 'lun is Optional: FC target lun number' + type: integer + format: int32 + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide names (WWNs)' + type: array + items: + type: string + wwids: + description: 'wwids Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.' + type: array + items: + type: string + flexVolume: + description: flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. + type: object + required: + - driver + properties: + driver: + description: driver is the name of the driver to use for this volume. + type: string + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + description: 'options is Optional: this field holds extra command options if any.' + type: object + additionalProperties: + type: string + readOnly: + description: 'readOnly is Optional: defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'secretRef is Optional: secretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + flocker: + description: flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + type: object + properties: + datasetName: + description: datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. This is unique identifier of a Flocker dataset + type: string + gcePersistentDisk: + description: 'gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: object + required: + - pdName + properties: + fsType: + description: 'fsType is filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: integer + format: int32 + pdName: + description: 'pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + gitRepo: + description: 'gitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod''s container.' + type: object + required: + - repository + properties: + directory: + description: directory is the target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified revision. + type: string + glusterfs: + description: 'glusterfs represents a Glusterfs mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + type: object + required: + - endpoints + - path + properties: + endpoints: + description: 'endpoints is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'readOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + hostPath: + description: 'hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath --- TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not mount host directories as read/write.' + type: object + required: + - path + properties: + path: + description: 'path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + iscsi: + description: 'iscsi represents an ISCSI Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + type: object + required: + - iqn + - lun + - targetPortal + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + initiatorName: + description: initiatorName is the custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iscsiInterface is the interface Name that uses an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + type: integer + format: int32 + portals: + description: portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: array + items: + type: string + readOnly: + description: readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI target and initiator authentication + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + targetPortal: + description: targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: string + name: + description: 'name of the volume. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'nfs represents an NFS mount on the host that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: object + required: + - path + - server + properties: + path: + description: 'path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'readOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + persistentVolumeClaim: + description: 'persistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: object + required: + - claimName + properties: + claimName: + description: 'claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: readOnly Will force the ReadOnly setting in VolumeMounts. Default false. + type: boolean + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine + type: object + required: + - pdID + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller persistent disk + type: string + portworxVolume: + description: portworxVolume represents a portworx volume attached and mounted on kubelets host machine + type: object + required: + - volumeID + properties: + fsType: + description: fSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx volume + type: string + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: defaultMode are the mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: sources is the list of volume projections + type: array + items: + description: Projection that may be projected along with other supported volume types + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: items if unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver. + type: string + expirationSeconds: + description: expirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes. + type: integer + format: int64 + path: + description: path is the path relative to the mount point of the file to project the token into. + type: string + quobyte: + description: quobyte represents a Quobyte mount on the host that shares a pod's lifetime + type: object + required: + - registry + - volume + properties: + group: + description: group to map volume access to Default is no group + type: string + readOnly: + description: readOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. + type: boolean + registry: + description: registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes + type: string + tenant: + description: tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: user to map volume access to Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already created Quobyte volume by name. + type: string + rbd: + description: 'rbd represents a Rados Block Device mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + type: object + required: + - image + - monitors + properties: + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + image: + description: 'image is the rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'monitors is a collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: array + items: + type: string + pool: + description: 'pool is the rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'secretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'user is the rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + scaleIO: + description: scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + type: object + required: + - gateway + - secretRef + - system + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication with Gateway, default false + type: boolean + storageMode: + description: storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system as configured in ScaleIO. + type: string + volumeName: + description: volumeName is the name of a volume already created in the ScaleIO system that is associated with this volume source. + type: string + secret: + description: 'secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: object + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: items If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + storageos: + description: storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + type: object + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeName: + description: volumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. + type: string + volumeNamespace: + description: volumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created. + type: string + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + type: object + required: + - volumePath + properties: + fsType: + description: fsType is filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies vSphere volume vmdk + type: string + permissions: + type: array + items: + description: StrategyDeploymentPermissions describe the rbac rules and service account needed by the install strategy + type: object + required: + - rules + - serviceAccountName + properties: + rules: + type: array + items: + description: PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to. + type: object + required: + - verbs + properties: + apiGroups: + description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. + type: array + items: + type: string + nonResourceURLs: + description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + type: array + items: + type: string + resourceNames: + description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + type: array + items: + type: string + resources: + description: Resources is a list of resources this rule applies to. '*' represents all resources. + type: array + items: + type: string + verbs: + description: Verbs is a list of Verbs that apply to ALL the ResourceKinds contained in this rule. '*' represents all verbs. + type: array + items: + type: string + serviceAccountName: + type: string + strategy: + type: string + installModes: + description: InstallModes specify supported installation types + type: array + items: + description: InstallMode associates an InstallModeType with a flag representing if the CSV supports it + type: object + required: + - supported + - type + properties: + supported: + type: boolean + type: + description: InstallModeType is a supported type of install mode for CSV installation + type: string + keywords: + description: A list of keywords describing the operator. + type: array + items: + type: string + labels: + description: Map of string keys and values that can be used to organize and categorize (scope and select) objects. + type: object + additionalProperties: + type: string + links: + description: A list of links related to the operator. + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + maintainers: + description: A list of organizational entities maintaining the operator. + type: array + items: + type: object + properties: + email: + type: string + name: + type: string + maturity: + type: string + minKubeVersion: + type: string + nativeAPIs: + type: array + items: + description: GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling + type: object + required: + - group + - kind + - version + properties: + group: + type: string + kind: + type: string + version: + type: string + provider: + description: The publishing entity behind the operator. + type: object + properties: + name: + type: string + url: + type: string + relatedImages: + description: List any related images, or other container images that your Operator might require to perform their functions. This list should also include operand images as well. All image references should be specified by digest (SHA) and not by tag. This field is only used during catalog creation and plays no part in cluster runtime. + type: array + items: + type: object + required: + - image + - name + properties: + image: + type: string + name: + type: string + replaces: + description: The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV. + type: string + selector: + description: Label selector for related resources. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + skips: + description: The name(s) of one or more CSV(s) that should be skipped in the upgrade graph. Should match the `metadata.Name` field of the CSV that should be skipped. This field is only used during catalog creation and plays no part in cluster runtime. + type: array + items: + type: string + version: + type: string + webhookdefinitions: + type: array + items: + description: WebhookDescription provides details to OLM about required webhooks + type: object + required: + - admissionReviewVersions + - generateName + - sideEffects + - type + properties: + admissionReviewVersions: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + default: 443 + maximum: 65535 + minimum: 1 + conversionCRDs: + type: array + items: + type: string + deploymentName: + type: string + failurePolicy: + description: FailurePolicyType specifies a failure policy that defines how unrecognized errors from the admission endpoint are handled. + type: string + generateName: + type: string + matchPolicy: + description: MatchPolicyType specifies the type of match policy. + type: string + objectSelector: + 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. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + reinvocationPolicy: + description: ReinvocationPolicyType specifies what type of policy the admission hook uses. + type: string + rules: + type: array + items: + description: RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid. + type: object + properties: + apiGroups: + description: APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required. + type: array + items: + type: string + x-kubernetes-list-type: atomic + apiVersions: + description: APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required. + type: array + items: + type: string + x-kubernetes-list-type: atomic + operations: + description: Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required. + type: array + items: + description: OperationType specifies an operation for a request. + type: string + x-kubernetes-list-type: atomic + resources: + description: "Resources is a list of resources this rule applies to. \n For example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources. \n If wildcard is present, the validation rule will ensure resources do not overlap with each other. \n Depending on the enclosing object, subresources might not be allowed. Required." + type: array + items: + type: string + x-kubernetes-list-type: atomic + scope: + description: scope specifies the scope of this rule. Valid values are "Cluster", "Namespaced", and "*" "Cluster" means that only cluster-scoped resources will match this rule. Namespace API objects are cluster-scoped. "Namespaced" means that only namespaced resources will match this rule. "*" means that there are no scope restrictions. Subresources match the scope of their parent resource. Default is "*". + type: string + sideEffects: + description: SideEffectClass specifies the types of side effects a webhook may have. + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + type: integer + format: int32 + type: + description: WebhookAdmissionType is the type of admission webhooks supported by OLM + type: string + enum: + - ValidatingAdmissionWebhook + - MutatingAdmissionWebhook + - ConversionWebhook + webhookPath: + type: string + status: + description: ClusterServiceVersionStatus represents information about the status of a CSV. Status may trail the actual state of a system. + type: object + properties: + certsLastUpdated: + description: Last time the owned APIService certs were updated + type: string + format: date-time + certsRotateAt: + description: Time the owned APIService certs will rotate next + type: string + format: date-time + cleanup: + description: CleanupStatus represents information about the status of cleanup while a CSV is pending deletion + type: object + properties: + pendingDeletion: + description: PendingDeletion is the list of custom resource objects that are pending deletion and blocked on finalizers. This indicates the progress of cleanup that is blocking CSV deletion or operator uninstall. + type: array + items: + description: ResourceList represents a list of resources which are of the same Group/Kind + type: object + required: + - group + - instances + - kind + properties: + group: + type: string + instances: + type: array + items: + type: object + required: + - name + properties: + name: + type: string + namespace: + description: Namespace can be empty for cluster-scoped resources + type: string + kind: + type: string + conditions: + description: List of conditions, a history of state transitions + type: array + items: + description: Conditions appear in the status as a record of state transitions on the ClusterServiceVersion + type: object + properties: + lastTransitionTime: + description: Last time the status transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time we updated the status + type: string + format: date-time + message: + description: A human readable message indicating details about why the ClusterServiceVersion is in this condition. + type: string + phase: + description: Condition of the ClusterServiceVersion + type: string + reason: + description: A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. e.g. 'RequirementsNotMet' + type: string + lastTransitionTime: + description: Last time the status transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time we updated the status + type: string + format: date-time + message: + description: A human readable message indicating details about why the ClusterServiceVersion is in this condition. + type: string + phase: + description: Current condition of the ClusterServiceVersion + type: string + reason: + description: A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. e.g. 'RequirementsNotMet' + type: string + requirementStatus: + description: The status of each requirement for this CSV + type: array + items: + type: object + required: + - group + - kind + - message + - name + - status + - version + properties: + dependents: + type: array + items: + description: DependentStatus is the status for a dependent requirement (to prevent infinite nesting) + type: object + required: + - group + - kind + - status + - version + properties: + group: + type: string + kind: + type: string + message: + type: string + status: + description: StatusReason is a camelcased reason for the status of a RequirementStatus or DependentStatus + type: string + uuid: + type: string + version: + type: string + group: + type: string + kind: + type: string + message: + type: string + name: + type: string + status: + description: StatusReason is a camelcased reason for the status of a RequirementStatus or DependentStatus + type: string + uuid: + type: string + version: + type: string + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: installplans.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: InstallPlan + listKind: InstallPlanList + plural: installplans + shortNames: + - ip + singular: installplan + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The first CSV in the list of clusterServiceVersionNames + jsonPath: .spec.clusterServiceVersionNames[0] + name: CSV + type: string + - description: The approval mode + jsonPath: .spec.approval + name: Approval + type: string + - jsonPath: .spec.approved + name: Approved + type: boolean + name: v1alpha1 + schema: + openAPIV3Schema: + description: InstallPlan defines the installation of a set of operators. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: InstallPlanSpec defines a set of Application resources to be installed + type: object + required: + - approval + - approved + - clusterServiceVersionNames + properties: + approval: + description: Approval is the user approval policy for an InstallPlan. It must be one of "Automatic" or "Manual". + type: string + approved: + type: boolean + clusterServiceVersionNames: + type: array + items: + type: string + generation: + type: integer + source: + type: string + sourceNamespace: + type: string + status: + description: "InstallPlanStatus represents the information about the status of steps required to complete installation. \n Status may trail the actual state of a system." + type: object + required: + - catalogSources + - phase + properties: + attenuatedServiceAccountRef: + description: AttenuatedServiceAccountRef references the service account that is used to do scoped operator install. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + bundleLookups: + description: BundleLookups is the set of in-progress requests to pull and unpackage bundle content to the cluster. + type: array + items: + description: BundleLookup is a request to pull and unpackage the content of a bundle to the cluster. + type: object + required: + - catalogSourceRef + - identifier + - path + - replaces + properties: + catalogSourceRef: + description: CatalogSourceRef is a reference to the CatalogSource the bundle path was resolved from. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + conditions: + description: Conditions represents the overall state of a BundleLookup. + type: array + items: + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time the condition was probed. + type: string + format: date-time + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + identifier: + description: Identifier is the catalog-unique name of the operator (the name of the CSV for bundles that contain CSVs) + type: string + path: + description: Path refers to the location of a bundle to pull. It's typically an image reference. + type: string + properties: + description: The effective properties of the unpacked bundle. + type: string + replaces: + description: Replaces is the name of the bundle to replace with the one found at Path. + type: string + catalogSources: + type: array + items: + type: string + conditions: + type: array + items: + description: InstallPlanCondition represents the overall status of the execution of an InstallPlan. + type: object + properties: + lastTransitionTime: + type: string + format: date-time + lastUpdateTime: + type: string + format: date-time + message: + type: string + reason: + description: ConditionReason is a camelcased reason for the state transition. + type: string + status: + type: string + type: + description: InstallPlanConditionType describes the state of an InstallPlan at a certain point as a whole. + type: string + message: + description: Message is a human-readable message containing detailed information that may be important to understanding why the plan has its current status. + type: string + phase: + description: InstallPlanPhase is the current status of a InstallPlan as a whole. + type: string + plan: + type: array + items: + description: Step represents the status of an individual step in an InstallPlan. + type: object + required: + - resolving + - resource + - status + properties: + optional: + type: boolean + resolving: + type: string + resource: + description: StepResource represents the status of a resource to be tracked by an InstallPlan. + type: object + required: + - group + - kind + - name + - sourceName + - sourceNamespace + - version + properties: + group: + type: string + kind: + type: string + manifest: + type: string + name: + type: string + sourceName: + type: string + sourceNamespace: + type: string + version: + type: string + status: + description: StepStatus is the current status of a particular resource an in InstallPlan + type: string + startTime: + description: StartTime is the time when the controller began applying the resources listed in the plan to the cluster. + type: string + format: date-time + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: olmconfigs.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: OLMConfig + listKind: OLMConfigList + plural: olmconfigs + singular: olmconfig + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OLMConfig is a resource responsible for configuring OLM. + type: object + required: + - metadata + 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: OLMConfigSpec is the spec for an OLMConfig resource. + type: object + properties: + features: + description: Features contains the list of configurable OLM features. + type: object + properties: + disableCopiedCSVs: + description: DisableCopiedCSVs is used to disable OLM's "Copied CSV" feature for operators installed at the cluster scope, where a cluster scoped operator is one that has been installed in an OperatorGroup that targets all namespaces. When reenabled, OLM will recreate the "Copied CSVs" for each cluster scoped operator. + type: boolean + packageServerSyncInterval: + description: PackageServerSyncInterval is used to define the sync interval for packagerserver pods. Packageserver pods periodically check the status of CatalogSources; this specifies the period using duration format (e.g. "60m"). For this parameter, only hours ("h"), minutes ("m"), and seconds ("s") may be specified. When not specified, the period defaults to the value specified within the packageserver. + type: string + pattern: ^([0-9]+(\.[0-9]+)?(s|m|h))+$ + status: + description: OLMConfigStatus is the status for an OLMConfig resource. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: operatorconditions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: OperatorCondition + listKind: OperatorConditionList + plural: operatorconditions + shortNames: + - condition + singular: operatorcondition + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. + type: object + required: + - metadata + 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: OperatorConditionSpec allows a cluster admin to convey information about the state of an operator to OLM, potentially overriding state reported by the operator. + type: object + properties: + deployments: + type: array + items: + type: string + overrides: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + serviceAccounts: + type: array + items: + type: string + status: + description: OperatorConditionStatus allows an operator to convey information its state to OLM. The status may trail the actual state of a system. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + served: true + storage: false + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. + type: object + required: + - metadata + 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: OperatorConditionSpec allows an operator to report state to OLM and provides cluster admin with the ability to manually override state reported by the operator. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + deployments: + type: array + items: + type: string + overrides: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + serviceAccounts: + type: array + items: + type: string + status: + description: OperatorConditionStatus allows OLM to convey which conditions have been observed. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: operatorgroups.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: OperatorGroup + listKind: OperatorGroupList + plural: operatorgroups + shortNames: + - og + singular: operatorgroup + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OperatorGroup is the unit of multitenancy for OLM managed operators. It constrains the installation of operators in its namespace to a specified set of target namespaces. + type: object + required: + - metadata + 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: OperatorGroupSpec is the spec for an OperatorGroup resource. + type: object + default: + upgradeStrategy: Default + properties: + selector: + description: Selector selects the OperatorGroup's target namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + serviceAccountName: + description: ServiceAccountName is the admin specified service account which will be used to deploy operator(s) in this operator group. + type: string + staticProvidedAPIs: + description: Static tells OLM not to update the OperatorGroup's providedAPIs annotation + type: boolean + targetNamespaces: + description: TargetNamespaces is an explicit set of namespaces to target. If it is set, Selector is ignored. + type: array + items: + type: string + x-kubernetes-list-type: set + upgradeStrategy: + description: "UpgradeStrategy defines the upgrade strategy for operators in the namespace. There are currently two supported upgrade strategies: \n Default: OLM will only allow clusterServiceVersions to move to the replacing phase from the succeeded phase. This effectively means that OLM will not allow operators to move to the next version if an installation or upgrade has failed. \n TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the replacing phase from the succeeded phase or from the failed phase. Additionally, OLM will generate new installPlans when a subscription references a failed installPlan and the catalog has been updated with a new upgrade for the existing set of operators. \n WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result in unexpected behavior or unrecoverable data loss unless you have deep understanding of the set of operators being managed in the namespace." + type: string + default: Default + enum: + - Default + - TechPreviewUnsafeFailForward + status: + description: OperatorGroupStatus is the status for an OperatorGroupResource. + type: object + required: + - lastUpdated + properties: + conditions: + description: Conditions is an array of the OperatorGroup's conditions. + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + 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. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + 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. + type: integer + format: int64 + minimum: 0 + 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. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + 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) + type: string + 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])$ + lastUpdated: + description: LastUpdated is a timestamp of the last time the OperatorGroup's status was Updated. + type: string + format: date-time + namespaces: + description: Namespaces is the set of target namespaces for the OperatorGroup. + type: array + items: + type: string + x-kubernetes-list-type: set + serviceAccountRef: + description: ServiceAccountRef references the service account object specified. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: true + subresources: + status: {} + - name: v1alpha2 + schema: + openAPIV3Schema: + description: OperatorGroup is the unit of multitenancy for OLM managed operators. It constrains the installation of operators in its namespace to a specified set of target namespaces. + type: object + required: + - metadata + 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: OperatorGroupSpec is the spec for an OperatorGroup resource. + type: object + properties: + selector: + description: Selector selects the OperatorGroup's target namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + serviceAccountName: + description: ServiceAccountName is the admin specified service account which will be used to deploy operator(s) in this operator group. + type: string + staticProvidedAPIs: + description: Static tells OLM not to update the OperatorGroup's providedAPIs annotation + type: boolean + targetNamespaces: + description: TargetNamespaces is an explicit set of namespaces to target. If it is set, Selector is ignored. + type: array + items: + type: string + status: + description: OperatorGroupStatus is the status for an OperatorGroupResource. + type: object + required: + - lastUpdated + properties: + lastUpdated: + description: LastUpdated is a timestamp of the last time the OperatorGroup's status was Updated. + type: string + format: date-time + namespaces: + description: Namespaces is the set of target namespaces for the OperatorGroup. + type: array + items: + type: string + serviceAccountRef: + description: ServiceAccountRef references the service account object specified. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: false + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: operators.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: Operator + listKind: OperatorList + plural: operators + singular: operator + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Operator represents a cluster operator. + type: object + 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: OperatorSpec defines the desired state of Operator + type: object + status: + description: OperatorStatus defines the observed state of an Operator and its components + type: object + properties: + components: + description: Components describes resources that compose the operator. + type: object + required: + - labelSelector + properties: + labelSelector: + description: LabelSelector is a label query over a set of resources used to select the operator's components + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + refs: + description: Refs are a set of references to the operator's component resources, selected with LabelSelector. + type: array + items: + description: RichReference is a reference to a resource, enriched with its status conditions. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + conditions: + description: Conditions represents the latest state of the component. + type: array + items: + description: Condition represent the latest available observations of an component's state. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time the condition was probed + type: string + format: date-time + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.0 + creationTimestamp: null + name: subscriptions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: Subscription + listKind: SubscriptionList + plural: subscriptions + shortNames: + - sub + - subs + singular: subscription + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The package subscribed to + jsonPath: .spec.name + name: Package + type: string + - description: The catalog source for the specified package + jsonPath: .spec.source + name: Source + type: string + - description: The channel of updates to subscribe to + jsonPath: .spec.channel + name: Channel + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Subscription keeps operators up to date by tracking changes to Catalogs. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SubscriptionSpec defines an Application that can be installed + type: object + required: + - name + - source + - sourceNamespace + properties: + channel: + type: string + config: + description: SubscriptionConfig contains configuration specified for a subscription. + type: object + properties: + affinity: + description: If specified, overrides the pod's scheduling constraints. nil sub-attributes will *not* override the original values in the pod.spec for those sub-attributes. Use empty object ({}) to erase original sub-attribute values. + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaceSelector: + description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + annotations: + description: Annotations is an unstructured key value map stored with each Deployment, Pod, APIService in the Operator. Typically, annotations may be set by external tools to store and retrieve arbitrary metadata. Use this field to pre-define annotations that OLM should add to each of the Subscription's deployments, pods, and apiservices. + type: object + additionalProperties: + type: string + env: + description: Env is a list of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: EnvFrom is a list of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Immutable. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + resources: + description: 'Resources represents compute resources required by this container. Immutable. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: Selector is the label selector for pods to be configured. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + tolerations: + description: Tolerations are the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + volumeMounts: + description: List of VolumeMounts to set in the container. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + volumes: + description: List of Volumes to set in the podSpec. + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + awsElasticBlockStore: + description: 'awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: object + required: + - volumeID + properties: + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).' + type: integer + format: int32 + readOnly: + description: 'readOnly value true will force the readOnly setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + azureDisk: + description: azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + type: object + required: + - diskName + - diskURI + properties: + cachingMode: + description: 'cachingMode is the Host Caching mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data disk in the blob storage + type: string + fsType: + description: fsType is Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared' + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + azureFile: + description: azureFile represents an Azure File Service mount on the host and bind mount to the pod. + type: object + required: + - secretName + - shareName + properties: + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of secret that contains Azure Storage Account Name and Key + type: string + shareName: + description: shareName is the azure share Name + type: string + cephfs: + description: cephFS represents a Ceph FS mount on the host that shares a pod's lifetime + type: object + required: + - monitors + properties: + monitors: + description: 'monitors is Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: array + items: + type: string + path: + description: 'path is Optional: Used as the mounted root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'user is optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + cinder: + description: 'cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: object + required: + - volumeID + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'secretRef is optional: points to a secret object containing parameters used to connect to OpenStack.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeID: + description: 'volumeID used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + configMap: + description: configMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: 'defaultMode is optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + csi: + description: csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). + type: object + required: + - driver + properties: + driver: + description: driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: fsType to mount. Ex. "ext4", "xfs", "ntfs". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: nodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + readOnly: + description: readOnly specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + description: volumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values. + type: object + additionalProperties: + type: string + downwardAPI: + description: downwardAPI represents downward API about the pod that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits to use on created files by default. Must be a Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: Items is a list of downward API volume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + emptyDir: + description: 'emptyDir represents a temporary directory that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: object + properties: + medium: + description: 'medium represents what type of storage medium should back this directory. The default is "" which means to use the node''s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + description: 'sizeLimit is the total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + ephemeral: + description: "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed. \n Use this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity tracking are needed, c) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource for more information on the connection between this volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod. \n Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information. \n A pod can use both types of ephemeral volumes and persistent volumes at the same time." + type: object + properties: + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long). \n An existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster. \n This field is read-only and no changes will be made by Kubernetes to the PVC after it has been created. \n Required, must not be nil." + type: object + required: + - spec + properties: + metadata: + description: May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here. + type: object + properties: + accessModes: + description: 'accessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + type: array + items: + type: string + dataSource: + description: 'dataSource field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source. When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. If the namespace is specified, then dataSourceRef will not be copied to dataSource.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + dataSourceRef: + description: 'dataSourceRef specifies the object from which to populate the volume with data, if a non-empty volume is desired. This may be any object from a non-empty API group (non core object) or a PersistentVolumeClaim object. When this field is specified, volume binding will only succeed if the type of the specified object matches some installed volume populator or dynamic provisioner. This field will replace the functionality of the dataSource field and as such if both fields are non-empty, they must have the same value. For backwards compatibility, when namespace isn''t specified in dataSourceRef, both fields (dataSource and dataSourceRef) will be set to the same value automatically if one of them is empty and the other is non-empty. When namespace is specified in dataSourceRef, dataSource isn''t set to the same value and must be empty. There are three important differences between dataSource and dataSourceRef: * While dataSource only allows two specific types of objects, dataSourceRef allows any non-core object, as well as PersistentVolumeClaim objects. * While dataSource ignores disallowed values (dropping them), dataSourceRef preserves all values, and generates an error if a disallowed value is specified. * While dataSource only allows local objects, dataSourceRef allows objects in any namespaces. (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + namespace: + description: Namespace is the namespace of resource being referenced Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + resources: + description: 'resources represents the minimum resources the volume should have. If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + type: object + properties: + claims: + description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. \n This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. \n This field is immutable. It can only be set for containers." + type: array + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + type: object + required: + - name + properties: + name: + description: Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. + type: string + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: selector is a label query over volumes to consider for binding. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + storageClassName: + description: 'storageClassName is the name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding reference to the PersistentVolume backing this claim. + type: string + fc: + description: fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + type: object + properties: + fsType: + description: 'fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + lun: + description: 'lun is Optional: FC target lun number' + type: integer + format: int32 + readOnly: + description: 'readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC target worldwide names (WWNs)' + type: array + items: + type: string + wwids: + description: 'wwids Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.' + type: array + items: + type: string + flexVolume: + description: flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. + type: object + required: + - driver + properties: + driver: + description: driver is the name of the driver to use for this volume. + type: string + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + description: 'options is Optional: this field holds extra command options if any.' + type: object + additionalProperties: + type: string + readOnly: + description: 'readOnly is Optional: defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'secretRef is Optional: secretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + flocker: + description: flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + type: object + properties: + datasetName: + description: datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of the dataset. This is unique identifier of a Flocker dataset + type: string + gcePersistentDisk: + description: 'gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: object + required: + - pdName + properties: + fsType: + description: 'fsType is filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: integer + format: int32 + pdName: + description: 'pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + gitRepo: + description: 'gitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod''s container.' + type: object + required: + - repository + properties: + directory: + description: directory is the target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash for the specified revision. + type: string + glusterfs: + description: 'glusterfs represents a Glusterfs mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + type: object + required: + - endpoints + - path + properties: + endpoints: + description: 'endpoints is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'readOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + hostPath: + description: 'hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath --- TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not mount host directories as read/write.' + type: object + required: + - path + properties: + path: + description: 'path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + iscsi: + description: 'iscsi represents an ISCSI Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + type: object + required: + - iqn + - lun + - targetPortal + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + initiatorName: + description: initiatorName is the custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iscsiInterface is the interface Name that uses an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target Lun number. + type: integer + format: int32 + portals: + description: portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: array + items: + type: string + readOnly: + description: readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret for iSCSI target and initiator authentication + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + targetPortal: + description: targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: string + name: + description: 'name of the volume. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'nfs represents an NFS mount on the host that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: object + required: + - path + - server + properties: + path: + description: 'path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'readOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + persistentVolumeClaim: + description: 'persistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: object + required: + - claimName + properties: + claimName: + description: 'claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: readOnly Will force the ReadOnly setting in VolumeMounts. Default false. + type: boolean + photonPersistentDisk: + description: photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine + type: object + required: + - pdID + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies Photon Controller persistent disk + type: string + portworxVolume: + description: portworxVolume represents a portworx volume attached and mounted on kubelets host machine + type: object + required: + - volumeID + properties: + fsType: + description: fSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies a Portworx volume + type: string + projected: + description: projected items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: defaultMode are the mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: sources is the list of volume projections + type: array + items: + description: Projection that may be projected along with other supported volume types + type: object + properties: + configMap: + description: configMap information about the configMap data to project + type: object + properties: + items: + description: items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional specify whether the ConfigMap or its keys must be defined + type: boolean + downwardAPI: + description: downwardAPI information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secret: + description: secret information about the secret data to project + type: object + properties: + items: + description: items if unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: optional field specify whether the Secret or its key must be defined + type: boolean + serviceAccountToken: + description: serviceAccountToken is information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver. + type: string + expirationSeconds: + description: expirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes. + type: integer + format: int64 + path: + description: path is the path relative to the mount point of the file to project the token into. + type: string + quobyte: + description: quobyte represents a Quobyte mount on the host that shares a pod's lifetime + type: object + required: + - registry + - volume + properties: + group: + description: group to map volume access to Default is no group + type: string + readOnly: + description: readOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. + type: boolean + registry: + description: registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes + type: string + tenant: + description: tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: user to map volume access to Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references an already created Quobyte volume by name. + type: string + rbd: + description: 'rbd represents a Rados Block Device mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + type: object + required: + - image + - monitors + properties: + fsType: + description: 'fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + image: + description: 'image is the rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'monitors is a collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: array + items: + type: string + pool: + description: 'pool is the rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'secretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'user is the rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + scaleIO: + description: scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + type: object + required: + - gateway + - secretRef + - system + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: gateway is the host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name of the ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + sslEnabled: + description: sslEnabled Flag enable/disable SSL communication with Gateway, default false + type: boolean + storageMode: + description: storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO Storage Pool associated with the protection domain. + type: string + system: + description: system is the name of the storage system as configured in ScaleIO. + type: string + volumeName: + description: volumeName is the name of a volume already created in the ScaleIO system that is associated with this volume source. + type: string + secret: + description: 'secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: object + properties: + defaultMode: + description: 'defaultMode is Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: items If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: key is the key to project. + type: string + mode: + description: 'mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + optional: + description: optional field specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: 'secretName is the name of the secret in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + storageos: + description: storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + type: object + properties: + fsType: + description: fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: secretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeName: + description: volumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. + type: string + volumeNamespace: + description: volumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created. + type: string + vsphereVolume: + description: vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + type: object + required: + - volumePath + properties: + fsType: + description: fsType is filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage Policy Based Management (SPBM) profile name. + type: string + volumePath: + description: volumePath is the path that identifies vSphere volume vmdk + type: string + installPlanApproval: + description: Approval is the user approval policy for an InstallPlan. It must be one of "Automatic" or "Manual". + type: string + name: + type: string + source: + type: string + sourceNamespace: + type: string + startingCSV: + type: string + status: + type: object + required: + - lastUpdated + properties: + catalogHealth: + description: CatalogHealth contains the Subscription's view of its relevant CatalogSources' status. It is used to determine SubscriptionStatusConditions related to CatalogSources. + type: array + items: + description: SubscriptionCatalogHealth describes the health of a CatalogSource the Subscription knows about. + type: object + required: + - catalogSourceRef + - healthy + - lastUpdated + properties: + catalogSourceRef: + description: CatalogSourceRef is a reference to a CatalogSource. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + healthy: + description: Healthy is true if the CatalogSource is healthy; false otherwise. + type: boolean + lastUpdated: + description: LastUpdated represents the last time that the CatalogSourceHealth changed + type: string + format: date-time + conditions: + description: Conditions is a list of the latest available observations about a Subscription's current state. + type: array + items: + description: SubscriptionCondition represents the latest available observations of a Subscription's state. + type: object + required: + - status + - type + properties: + lastHeartbeatTime: + description: LastHeartbeatTime is the last time we got an update on a given condition + type: string + format: date-time + lastTransitionTime: + description: LastTransitionTime is the last time the condition transit from one status to another + type: string + format: date-time + message: + description: Message is a human-readable message indicating details about last transition. + type: string + reason: + description: Reason is a one-word CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of Subscription condition. + type: string + currentCSV: + description: CurrentCSV is the CSV the Subscription is progressing to. + type: string + installPlanGeneration: + description: InstallPlanGeneration is the current generation of the installplan + type: integer + installPlanRef: + description: InstallPlanRef is a reference to the latest InstallPlan that contains the Subscription's current CSV. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + installedCSV: + description: InstalledCSV is the CSV currently installed by the Subscription. + type: string + installplan: + description: 'Install is a reference to the latest InstallPlan generated for the Subscription. DEPRECATED: InstallPlanRef' + type: object + required: + - apiVersion + - kind + - name + - uuid + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + uuid: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + lastUpdated: + description: LastUpdated represents the last time that the Subscription status was updated. + type: string + format: date-time + reason: + description: Reason is the reason the Subscription was transitioned to its current state. + type: string + state: + description: State represents the current state of the Subscription + type: string + served: true + storage: true + subresources: + status: {} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/vizier_crd.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/vizier_crd.yaml new file mode 100644 index 000000000..b25d7b592 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/crds/vizier_crd.yaml @@ -0,0 +1,347 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: viziers.px.dev +spec: + group: px.dev + names: + kind: Vizier + listKind: VizierList + plural: viziers + singular: vizier + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Vizier is the Schema for the viziers 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: VizierSpec defines the desired state of Vizier + properties: + autopilot: + description: Autopilot should be set if running Pixie on GKE Autopilot. + type: boolean + clockConverter: + description: ClockConverter specifies which routine to use for converting + timestamps to a synced reference time. + enum: + - default + - grpc + type: string + cloudAddr: + description: CloudAddr is the address of the cloud instance that the + Vizier should be pointing to. + type: string + clusterName: + description: ClusterName is a name for the Vizier instance, usually + specifying which cluster the Vizier is deployed to. If not specified, + a random name will be generated. + type: string + customDeployKeySecret: + description: CustomDeployKeySecret is the name of the secret where + the deploy key is stored. + type: string + dataAccess: + description: DataAccess defines the level of data that may be accesssed + when executing a script on the cluster. If none specified, assumes + full data access. + enum: + - Full + - Restricted + type: string + dataCollectorParams: + description: DataCollectorParams specifies the set of params for configuring + the dataCollector. If no params are specified, defaults are used. + properties: + customPEMFlags: + additionalProperties: + type: string + description: This contains custom flags that should be passed + to the PEM via environment variables. + type: object + datastreamBufferSize: + description: DatastreamBufferSize is the data buffer size per + connection. Default size is 1 Mbyte. For high-throughput applications, + try increasing this number if experiencing data loss. + format: int32 + type: integer + datastreamBufferSpikeSize: + description: DatastreamBufferSpikeSize is the maximum temporary + size of a data stream buffer before processing. + format: int32 + type: integer + type: object + deployKey: + description: DeployKey is the deploy key associated with the Vizier + instance. This is used to link the Vizier to a specific user/org. + This is required unless specifying a CustomDeployKeySecret. + type: string + devCloudNamespace: + description: 'DevCloudNamespace should be specified only for dev versions + of Pixie cloud which have no ingress to help redirect traffic to + the correct service. The DevCloudNamespace is the namespace that + the dev Pixie cloud is running on, for example: "plc-dev".' + type: string + disableAutoUpdate: + description: DisableAutoUpdate specifies whether auto update should + be enabled for the Vizier instance. + type: boolean + leadershipElectionParams: + description: LeadershipElectionParams specifies configurable values + for the K8s leaderships elections which Vizier uses manage pod leadership. + properties: + electionPeriodMs: + description: ElectionPeriodMs defines how frequently Vizier attempts + to run a K8s leader election, in milliseconds. The period also + determines how long Vizier waits for a leader election response + back from the K8s API. If the K8s API is slow to respond, consider + increasing this number. + format: int64 + type: integer + type: object + patches: + additionalProperties: + type: string + description: Patches defines patches that should be applied to Vizier + resources. The key of the patch should be the name of the resource + that is patched. The value of the patch is the patch, encoded as + a string which follow the "strategic merge patch" rules for K8s. + type: object + pemMemoryLimit: + description: PemMemoryLimit is a memory limit applied specifically + to PEM pods. + type: string + pemMemoryRequest: + description: PemMemoryRequest is a memory request applied specifically + to PEM pods. It will automatically use the value of pemMemoryLimit + if not specified. + type: string + pod: + description: Pod defines the policy for creating Vizier pods. + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies the annotations to attach to + pods the operator creates. + type: object + labels: + additionalProperties: + type: string + description: Labels specifies the labels to attach to pods the + operator creates. + type: object + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + This field cannot be updated once the cluster is created.' + type: object + resources: + description: Resources is the resource requirements for a container. + This field cannot be updated once the cluster is created. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + securityContext: + description: The securityContext which should be set on non-privileged + pods. All pods which require privileged permissions will still + require a privileged securityContext. + properties: + enabled: + description: Whether a securityContext should be set on the + pod. In cases where no PSPs are applied to the cluster, + this is not necessary. + type: boolean + fsGroup: + description: A special supplemental group that applies to + all containers in a pod. + format: int64 + type: integer + runAsGroup: + description: The GID to run the entrypoint of the container + process. + format: int64 + type: integer + runAsUser: + description: The UID to run the entrypoint of the container + process. + format: int64 + type: integer + type: object + tolerations: + description: 'Tolerations allows scheduling pods on nodes with + matching taints. More info: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/: + This field cannot be updated once the cluster is created.' + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object + registry: + description: 'Registry specifies the image registry to use rather + than Pixie''s default registry (gcr.io). We expect any forward slashes + in Pixie''s image paths are replaced with a "-". For example: "gcr.io/pixie-oss/pixie-dev/vizier/metadata_server_image:latest" + should be pushed to "$registry/gcr.io-pixie-oss-pixie-dev-vizier-metadata_server_image:latest".' + type: string + useEtcdOperator: + description: UseEtcdOperator specifies whether the metadata service + should use etcd for storage. + type: boolean + version: + description: Version is the desired version of the Vizier instance. + type: string + type: object + status: + description: VizierStatus defines the observed state of Vizier + properties: + checksum: + description: A checksum of the last reconciled Vizier spec. If this + checksum does not match the checksum of the current vizier spec, + reconciliation should be performed. + format: byte + type: string + lastReconciliationPhaseTime: + description: LastReconciliationPhaseTime is the last time that the + ReconciliationPhase changed. + format: date-time + type: string + message: + description: Message is a human-readable message with details about + why the Vizier is in this condition. + type: string + operatorVersion: + description: OperatorVersion is the actual version of the Operator + instance. + type: string + reconciliationPhase: + description: ReconciliationPhase describes the state the Reconciler + is in for this Vizier. See the documentation above the ReconciliationPhase + type for more information. + type: string + sentryDSN: + description: SentryDSN is key for Viziers that is used to send errors + and stacktraces to Sentry. + type: string + version: + description: Version is the actual version of the Vizier instance. + type: string + vizierPhase: + description: VizierPhase is a high-level summary of where the Vizier + is in its lifecycle. + type: string + vizierReason: + description: VizierReason is a short, machine understandable string + that gives the reason for the transition into the Vizier's current + status. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/00_olm.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/00_olm.yaml new file mode 100644 index 000000000..fe058140f --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/00_olm.yaml @@ -0,0 +1,232 @@ +{{- $olmCRDFound := false }} +{{- $nsLookup := len (lookup "v1" "Namespace" "" "") }} +{{- range $index, $crdLookup := (lookup "apiextensions.k8s.io/v1" "CustomResourceDefinition" "" "").items -}}{{ if eq $crdLookup.metadata.name "operators.operators.coreos.com"}}{{ $olmCRDFound = true }}{{ end }}{{end}} +{{ if and (not $olmCRDFound) (not (eq $nsLookup 0))}}{{ fail "CRDs missing! Please deploy CRDs from https://github.com/pixie-io/pixie/tree/main/k8s/operator/helm/crds to continue with deploy." }}{{end}} +{{- $lookupLen := 0 -}}{{- $opLookup := (lookup "operators.coreos.com/v1" "OperatorGroup" "" "").items -}}{{if $opLookup }}{{ $lookupLen = len $opLookup }}{{ end }} +{{ if (or (eq (.Values.deployOLM | toString) "true") (and (not (eq (.Values.deployOLM | toString) "false")) (eq $lookupLen 0))) }} +{{ if not (eq .Values.olmNamespace .Release.Namespace) }} +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.olmNamespace }} +{{ end }} +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: olm-operator-serviceaccount + namespace: {{ .Values.olmNamespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:controller:operator-lifecycle-manager +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +- nonResourceURLs: ["*"] + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: olm-operator-cluster-binding-olm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:operator-lifecycle-manager +subjects: +- kind: ServiceAccount + name: olm-operator-serviceaccount + namespace: {{ .Values.olmNamespace }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: olm-operator + namespace: {{ .Values.olmNamespace }} + labels: + app: olm-operator +spec: + strategy: + type: RollingUpdate + replicas: 1 + selector: + matchLabels: + app: olm-operator + template: + metadata: + labels: + app: olm-operator + spec: + serviceAccountName: olm-operator-serviceaccount + containers: + - name: olm-operator + command: + - /bin/olm + args: + - --namespace + - $(OPERATOR_NAMESPACE) + - --writeStatusName + - "" + image: {{ if .Values.registry }}{{ .Values.registry }}/quay.io-operator-framework-{{ else }}quay.io/operator-framework/{{ end }}olm@sha256:1b6002156f568d722c29138575733591037c24b4bfabc67946f268ce4752c3e6 + ports: + - containerPort: 8080 + - containerPort: 8081 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + terminationMessagePolicy: FallbackToLogsOnError + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: olm-operator + resources: + requests: + cpu: 10m + memory: 160Mi + nodeSelector: + kubernetes.io/os: linux + tolerations: + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoExecute" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoExecute" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: catalog-operator + namespace: {{ .Values.olmNamespace }} + labels: + app: catalog-operator +spec: + strategy: + type: RollingUpdate + replicas: 1 + selector: + matchLabels: + app: catalog-operator + template: + metadata: + labels: + app: catalog-operator + spec: + serviceAccountName: olm-operator-serviceaccount + containers: + - name: catalog-operator + command: + - /bin/catalog + args: + - '--namespace' + - {{ .Values.olmNamespace }} + - --configmapServerImage={{ if .Values.registry }}{{ .Values.registry }}/quay.io-operator-framework-{{ else }}quay.io/operator-framework/{{ end }}configmap-operator-registry:latest + - --util-image + - {{ if .Values.registry }}{{ .Values.registry }}/quay.io-operator-framework-{{ else }}quay.io/operator-framework/{{ end }}olm@sha256:1b6002156f568d722c29138575733591037c24b4bfabc67946f268ce4752c3e6 + - --opmImage + - {{ if .Values.registry }}{{ .Values.registry }}/quay.io-operator-framework-{{ else }}quay.io/operator-framework/{{ end }}opm@sha256:d999588bd4e9509ec9e75e49adfb6582d256e9421e454c7fb5e9fe57e7b1aada + image: {{ if .Values.registry }}{{ .Values.registry }}/quay.io-operator-framework-{{ else }}quay.io/operator-framework/{{ end }}olm@sha256:1b6002156f568d722c29138575733591037c24b4bfabc67946f268ce4752c3e6 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + - containerPort: 8081 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + terminationMessagePolicy: FallbackToLogsOnError + env: + resources: + requests: + cpu: 10m + memory: 80Mi + nodeSelector: + kubernetes.io/os: linux + tolerations: + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoExecute" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoExecute" +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: aggregate-olm-edit + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" +rules: +- apiGroups: ["operators.coreos.com"] + resources: ["subscriptions"] + verbs: ["create", "update", "patch", "delete"] +- apiGroups: ["operators.coreos.com"] + resources: ["clusterserviceversions", "catalogsources", "installplans", "subscriptions"] + verbs: ["delete"] +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: aggregate-olm-view + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-view: "true" +rules: +- apiGroups: ["operators.coreos.com"] + resources: ["clusterserviceversions", "catalogsources", "installplans", "subscriptions", "operatorgroups"] + verbs: ["get", "list", "watch"] +- apiGroups: ["packages.operators.coreos.com"] + resources: ["packagemanifests", "packagemanifests/icon"] + verbs: ["get", "list", "watch"] +--- +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + name: olm-operators + namespace: {{ .Values.olmNamespace }} +spec: + targetNamespaces: + - {{ .Values.olmNamespace }} +{{- end}} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/01_px_olm.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/01_px_olm.yaml new file mode 100644 index 000000000..2c2921958 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/01_px_olm.yaml @@ -0,0 +1,13 @@ +{{ if not (eq .Values.olmOperatorNamespace .Release.Namespace) }} +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.olmOperatorNamespace }} +{{ end }} +--- +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + name: global-operators + namespace: {{ .Values.olmOperatorNamespace }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/02_catalog.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/02_catalog.yaml new file mode 100644 index 000000000..e7f68804a --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/02_catalog.yaml @@ -0,0 +1,37 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: CatalogSource +metadata: + name: pixie-operator-index + namespace: {{ .Values.olmOperatorNamespace }} + {{- if .Values.olmCatalogSource.annotations }} + annotations: {{ .Values.olmCatalogSource.annotations | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.olmCatalogSource.labels }} + labels: {{ .Values.olmCatalogSource.labels | toYaml | nindent 4 }} + {{- end }} +spec: + sourceType: grpc + image: {{ if .Values.registry }}{{ .Values.registry }}/gcr.io-pixie-oss-pixie-prod-operator-bundle_index:0.0.1{{ else }}gcr.io/pixie-oss/pixie-prod/operator/bundle_index:0.0.1{{ end }} + displayName: Pixie Vizier Operator + publisher: px.dev + updateStrategy: + registryPoll: + interval: 10m + grpcPodConfig: + tolerations: + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "amd64" + effect: "NoExecute" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoSchedule" + - key: "kubernetes.io/arch" + operator: "Equal" + value: "arm64" + effect: "NoExecute" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/03_subscription.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/03_subscription.yaml new file mode 100644 index 000000000..78223cc9e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/03_subscription.yaml @@ -0,0 +1,11 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: pixie-operator-subscription + namespace: {{ .Values.olmOperatorNamespace }} +spec: + channel: {{ .Values.olmBundleChannel }} + name: pixie-operator + source: pixie-operator-index + sourceNamespace: {{ .Values.olmOperatorNamespace }} + installPlanApproval: Automatic diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/04_vizier.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/04_vizier.yaml new file mode 100644 index 000000000..7c8ca65ad --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/04_vizier.yaml @@ -0,0 +1,100 @@ +apiVersion: px.dev/v1alpha1 +kind: Vizier +metadata: + name: {{ .Values.name }} + namespace: {{ .Release.Namespace }} +spec: + {{- if .Values.version }} + version: {{ .Values.version }} + {{- end }} + {{- if .Values.deployKey }} + deployKey: {{ .Values.deployKey }} + {{- end }} + {{- if .Values.customDeployKeySecret }} + customDeployKeySecret: {{ .Values.customDeployKeySecret }} + {{- end }} + cloudAddr: {{ .Values.cloudAddr }} + disableAutoUpdate: {{ .Values.disableAutoUpdate }} + useEtcdOperator: {{ .Values.useEtcdOperator }} + {{- if (.Values.global).cluster }} + clusterName: {{ .Values.global.cluster }} + {{- else if .Values.clusterName }} + clusterName: {{ .Values.clusterName }} + {{- end }} + {{- if .Values.devCloudNamespace }} + devCloudNamespace: {{ .Values.devCloudNamespace }} + {{- end }} + {{- if .Values.pemMemoryLimit }} + pemMemoryLimit: {{ .Values.pemMemoryLimit }} + {{- end }} + {{- if .Values.pemMemoryRequest }} + pemMemoryRequest: {{ .Values.pemMemoryRequest }} + {{- end }} + {{- if .Values.dataAccess }} + dataAccess: {{ .Values.dataAccess }} + {{- end }} + {{- if .Values.patches }} + patches: {{ .Values.patches | toYaml | nindent 4 }} + {{- end }} + {{- if ((.Values.global).images).registry }} + registry: {{ .Values.global.images.registry }} + {{- else if .Values.registry }} + registry: {{ .Values.registry }} + {{- end}} + {{- if .Values.autopilot }} + autopilot: {{ .Values.autopilot }} + {{- end}} + {{- if .Values.dataCollectorParams }} + dataCollectorParams: + {{- if .Values.dataCollectorParams.datastreamBufferSize }} + datastreamBufferSize: {{ .Values.dataCollectorParams.datastreamBufferSize }} + {{- end }} + {{- if .Values.dataCollectorParams.datastreamBufferSpikeSize }} + datastreamBufferSpikeSize: {{ .Values.dataCollectorParams.datastreamBufferSpikeSize }} + {{- end }} + {{- if .Values.dataCollectorParams.customPEMFlags }} + customPEMFlags: + {{- range $key, $value := .Values.dataCollectorParams.customPEMFlags}} + {{$key}}: "{{$value}}" + {{- end}} + {{- end }} + {{- end}} + {{- if .Values.leadershipElectionParams }} + leadershipElectionParams: + {{- if .Values.leadershipElectionParams.electionPeriodMs }} + electionPeriodMs: {{ .Values.leadershipElectionParams.electionPeriodMs }} + {{- end }} + {{- end }} + {{- if or .Values.pod.securityContext (or .Values.pod.nodeSelector (or .Values.pod.tolerations (or .Values.pod.annotations (or .Values.pod.labels .Values.pod.resources)))) }} + pod: + {{- if .Values.pod.annotations }} + annotations: {{ .Values.pod.annotations | toYaml | nindent 6 }} + {{- end }} + {{- if .Values.pod.labels }} + labels: {{ .Values.pod.labels | toYaml | nindent 6 }} + {{- end }} + {{- if .Values.pod.resources }} + resources: {{ .Values.pod.resources | toYaml | nindent 6 }} + {{- end }} + {{- if .Values.pod.nodeSelector }} + nodeSelector: {{ .Values.pod.nodeSelector | toYaml | nindent 6 }} + {{- end }} + {{- if .Values.pod.tolerations }} + tolerations: {{ .Values.pod.tolerations | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.pod.securityContext }} + securityContext: + enabled: {{ .Values.pod.securityContext.enabled }} + {{- if .Values.pod.securityContext.enabled }} + {{- if .Values.pod.securityContext.fsGroup }} + fsGroup: {{ .Values.pod.securityContext.fsGroup }} + {{- end }} + {{- if .Values.pod.securityContext.runAsUser }} + runAsUser: {{ .Values.pod.securityContext.runAsUser }} + {{- end }} + {{- if .Values.pod.securityContext.runAsGroup }} + runAsGroup: {{ .Values.pod.securityContext.runAsGroup }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter.yaml new file mode 100644 index 000000000..b1cde0c92 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter.yaml @@ -0,0 +1,25 @@ +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + helm.sh/hook: pre-delete + helm.sh/hook-delete-policy: hook-succeeded + name: vizier-deleter + namespace: '{{ .Release.Namespace }}' +spec: + template: + metadata: + name: vizier-deleter + spec: + containers: + - env: + - name: PL_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: PL_VIZIER_NAME + value: '{{ .Values.name }}' + image: gcr.io/pixie-oss/pixie-prod/operator-vizier_deleter:0.1.6 + name: delete-job + restartPolicy: Never + serviceAccountName: pl-deleter-service-account diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter_role.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter_role.yaml new file mode 100644 index 000000000..73e5ec7e4 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/templates/deleter_role.yaml @@ -0,0 +1,77 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pl-deleter-service-account +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: pl-deleter-cluster-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: pl-deleter-role +subjects: +- kind: ServiceAccount + name: pl-deleter-service-account + namespace: "{{ .Release.Namespace }}" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pl-deleter-cluster-role +rules: +# Allow actions on Kubernetes objects +- apiGroups: + - rbac.authorization.k8s.io + - etcd.database.coreos.com + - nats.io + resources: + - clusterroles + - clusterrolebindings + - persistentvolumes + - etcdclusters + - natsclusters + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pl-deleter-role +rules: +- apiGroups: + - "" + - apps + - rbac.authorization.k8s.io + - extensions + - batch + - policy + resources: + - configmaps + - secrets + - pods + - services + - deployments + - daemonsets + - persistentvolumes + - roles + - rolebindings + - serviceaccounts + - statefulsets + - cronjobs + - jobs + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: pl-deleter-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: pl-deleter-role +subjects: +- kind: ServiceAccount + name: pl-deleter-service-account + namespace: "{{ .Release.Namespace }}" diff --git a/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/values.yaml b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/values.yaml new file mode 100644 index 000000000..a3ffe7c9d --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/charts/pixie-operator-chart/values.yaml @@ -0,0 +1,75 @@ +## OLM configuration +# OLM is used for deploying and ensuring the operator is up-to-date. +# deployOLM indicates whether OLM should be deployed. This should only be +# disabled if an instance of OLM is already configured on the cluster. +# Should be string "true" if true, but "false" otherwise. If empty, defaults +# to whether OLM is present in the cluster. +deployOLM: "" +# The namespace that olm should run in. If olm has already been deployed +# to the cluster, this should be the namespace that olm is already running in. +olmNamespace: "olm" +# The namespace which olm operators should run in. If olm has already +# been deployed to the cluster, this should be the namespace that the olm operators +# are running in. +olmOperatorNamespace: "px-operator" +# The bundle channel which OLM should listen to for the Vizier operator bundles. +# Should be "stable" for production-versions of the operator, and "test" for release candidates. +olmBundleChannel: "stable" +# Optional annotations and labels for CatalogSource. +olmCatalogSource: + # Optional custom annotations to add to deployed pods managed by CatalogSource object. + annotations: {} + # Optional custom labels to add to deployed pods managed by CatalogSource object. + labels: {} +## Vizier configuration +# The name of the Vizier instance deployed to the cluster. +name: "pixie" +# The name of the cluster that the Vizier is monitoring. If empty, +# a random name will be generated. +clusterName: "" +# The version of the Vizier instance deployed to the cluster. If empty, +# the operator will automatically deploy the latest version. +version: "" +# The deploy key is used to link the deployed Vizier to a specific user/project. +# This is required if not specifying a customDeployKeySecret, and can be generated through the UI or CLI. +deployKey: "" +# The deploy key may be read from a custom secret in the Pixie namespace. This secret should be formatted where the +# key of the deploy key is "deploy-key". +customDeployKeySecret: "" +# Whether auto-update should be disabled. +disableAutoUpdate: false +# Whether the metadata service should use etcd for in-memory storage. Recommended +# only for clusters which do not have persistent volumes configured. +useEtcdOperator: false +# The address of the Pixie cloud instance that the Vizier should be connected to. +# This should only be updated when using a self-hosted version of Pixie Cloud. +cloudAddr: "withpixie.ai:443" +# DevCloudNamespace should be specified only for self-hosted versions of Pixie cloud which have no ingress to help +# redirect traffic to the correct service. The DevCloudNamespace is the namespace that the dev Pixie cloud is +# running on, for example: "plc-dev". +devCloudNamespace: "" +# A memory limit applied specifically to PEM pods. If none is specified, a default limit of 2Gi is set. +pemMemoryLimit: "" +# A memory request applied specifically to PEM pods. If none is specified, it will default to pemMemoryLimit. +pemMemoryRequest: "" +# DataAccess defines the level of data that may be accesssed when executing a script on the cluster. +dataAccess: "Full" +pod: + # Optional custom annotations to add to deployed pods. + annotations: {} + # Optional custom labels to add to deployed pods. + labels: {} + resources: {} + # limits: + # cpu: 500m + # memory: 7Gi + # requests: + # cpu: 100m + # memory: 5Gi + nodeSelector: {} + tolerations: [] +# A set of custom patches to apply to the deployed Vizier resources. +# The key should be the name of the resource to apply the patch to, and the value is the patch to apply. +# Currently, only a JSON format is accepted, such as: +# `{"spec": {"template": {"spec": { "tolerations": [{"key": "test", "operator": "Exists", "effect": "NoExecute" }]}}}}` +patches: {} diff --git a/charts/new-relic/nri-bundle/5.0.98/ci/test-values.yaml b/charts/new-relic/nri-bundle/5.0.98/ci/test-values.yaml new file mode 100644 index 000000000..7ba6c8c32 --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/ci/test-values.yaml @@ -0,0 +1,21 @@ +global: + licenseKey: 1234567890abcdef1234567890abcdef12345678 + cluster: test-cluster + +infrastructure: + enabled: true + +prometheus: + enabled: true + +webhook: + enabled: true + +ksm: + enabled: true + +kubeEvents: + enabled: true + +logging: + enabled: true diff --git a/charts/new-relic/nri-bundle/5.0.98/questions.yaml b/charts/new-relic/nri-bundle/5.0.98/questions.yaml new file mode 100644 index 000000000..de3fa9fea --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/questions.yaml @@ -0,0 +1,113 @@ +questions: +- variable: infrastructure.enabled + default: true + required: false + type: boolean + label: Enable Infrastructure + group: "Select Components" +- variable: prometheus.enabled + default: false + required: false + type: boolean + label: Enable Prometheus + group: "Select Components" +- variable: ksm.enabled + default: false + required: false + type: boolean + label: Enable KSM + group: "Select Components" + description: "This is mandatory if `Enable Infrastructure` is set to `true` and the user does not provide its own instance of KSM version >=1.8 and <=2.0" +- variable: webhook.enabled + default: true + required: false + type: boolean + label: Enable webhook + group: "Select Components" +- variable: kubeEvents.enabled + default: false + required: false + type: boolean + label: Enable Kube Events + group: "Select Components" +- variable: logging.enabled + default: false + required: false + type: boolean + label: Enable Logging + group: "Select Components" +- variable: newrelic-pixie.enabled + default: false + required: false + type: boolean + label: Enable New Relic Pixie Integration + group: "Select Components" + show_subquestion_if: true + subquestions: + - variable: newrelic-pixie.apiKey + default: "" + required: false + type: string + label: New Relic Pixie API Key + group: "Select Components" + description: "Required if deploying Pixie." +- variable: pixie-chart.enabled + default: false + required: false + type: boolean + label: Enable Pixie Chart + group: "Select Components" + show_subquestion_if: true + subquestions: + - variable: pixie-chart.deployKey + default: "" + required: false + type: string + label: Pixie Deploy Key + group: "Select Components" + description: "Required if deploying Pixie." + - variable: pixie-chart.clusterName + default: "" + required: false + type: string + label: Kubernetes Cluster Name for Pixie + group: "Select Components" + description: "Required if deploying Pixie." +- variable: newrelic-infra-operator.enabled + default: false + required: false + type: boolean + label: Enable New Relic Infra Operator + group: "Select Components" +- variable: metrics-adapter.enabled + default: false + required: false + type: boolean + label: Enable Metrics Adapter + group: "Select Components" +- variable: global.licenseKey + default: "xxxx" + required: true + type: string + label: New Relic License Key + group: "Global Settings" +- variable: global.cluster + default: "xxxx" + required: true + type: string + label: Name of Kubernetes Cluster for New Relic + group: "Global Settings" +- variable: global.lowDataMode + default: false + required: false + type: boolean + label: Enable Low Data Mode + description: "Reduces amount of data ingest by New Relic." + group: "Global Settings" +- variable: global.privileged + default: false + required: false + type: boolean + label: Enable Privileged Mode + description: "Allows for access to underlying node from container." + group: "Global Settings" diff --git a/charts/new-relic/nri-bundle/5.0.98/values.yaml b/charts/new-relic/nri-bundle/5.0.98/values.yaml new file mode 100644 index 000000000..47c58df8e --- /dev/null +++ b/charts/new-relic/nri-bundle/5.0.98/values.yaml @@ -0,0 +1,169 @@ +newrelic-infrastructure: + # newrelic-infrastructure.enabled -- Install the [`newrelic-infrastructure` chart](https://github.com/newrelic/nri-kubernetes/tree/main/charts/newrelic-infrastructure) + enabled: true + +nri-prometheus: + # nri-prometheus.enabled -- Install the [`nri-prometheus` chart](https://github.com/newrelic/nri-prometheus/tree/main/charts/nri-prometheus) + enabled: false + +nri-metadata-injection: + # nri-metadata-injection.enabled -- Install the [`nri-metadata-injection` chart](https://github.com/newrelic/k8s-metadata-injection/tree/main/charts/nri-metadata-injection) + enabled: true + +kube-state-metrics: + # kube-state-metrics.enabled -- Install the [`kube-state-metrics` chart](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-state-metrics) from the stable helm charts repository. + # This is mandatory if `infrastructure.enabled` is set to `true` and the user does not provide its own instance of KSM version >=1.8 and <=2.0. Note, kube-state-metrics v2+ disables labels/annotations + # metrics by default. You can enable the target labels/annotations metrics to be monitored by using the metricLabelsAllowlist/metricAnnotationsAllowList options described [here](https://github.com/prometheus-community/helm-charts/blob/159cd8e4fb89b8b107dcc100287504bb91bf30e0/charts/kube-state-metrics/values.yaml#L274) in + # your Kubernetes clusters. + enabled: false + +nri-kube-events: + # nri-kube-events.enabled -- Install the [`nri-kube-events` chart](https://github.com/newrelic/nri-kube-events/tree/main/charts/nri-kube-events) + enabled: false + +newrelic-logging: + # newrelic-logging.enabled -- Install the [`newrelic-logging` chart](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging) + enabled: false + +newrelic-pixie: + # newrelic-pixie.enabled -- Install the [`newrelic-pixie`](https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie) + enabled: false + +k8s-agents-operator: + # k8s-agents-operator.enabled -- Install the [`k8s-agents-operator` chart](https://github.com/newrelic/k8s-agents-operator/tree/main/charts/k8s-agents-operator) + enabled: false + +pixie-chart: + # pixie-chart.enabled -- Install the [`pixie-chart` chart](https://docs.pixielabs.ai/installing-pixie/install-schemes/helm/#3.-deploy) + enabled: false + +newrelic-infra-operator: + # newrelic-infra-operator.enabled -- Install the [`newrelic-infra-operator` chart](https://github.com/newrelic/newrelic-infra-operator/tree/main/charts/newrelic-infra-operator) (Beta) + enabled: false + +newrelic-prometheus-agent: + # newrelic-prometheus-agent.enabled -- Install the [`newrelic-prometheus-agent` chart](https://github.com/newrelic/newrelic-prometheus-configurator/tree/main/charts/newrelic-prometheus-agent) + enabled: false + +newrelic-k8s-metrics-adapter: + # newrelic-k8s-metrics-adapter.enabled -- Install the [`newrelic-k8s-metrics-adapter.` chart](https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/main/charts/newrelic-k8s-metrics-adapter) (Beta) + enabled: false + + +# -- change the behaviour globally to all the supported helm charts. +# See [user's guide of the common library](https://github.com/newrelic/helm-charts/blob/master/library/common-library/README.md) for further information. +# @default -- See [`values.yaml`](values.yaml) +global: + # -- The cluster name for the Kubernetes cluster. + cluster: "" + + # -- The license key for your New Relic Account. This will be preferred configuration option if both `licenseKey` and `customSecret` are specified. + licenseKey: "" + # -- The license key for your New Relic Account. This will be preferred configuration option if both `insightsKey` and `customSecret` are specified. + insightsKey: "" + # -- Name of the Secret object where the license key is stored + customSecretName: "" + # -- Key in the Secret object where the license key is stored + customSecretLicenseKey: "" + + # -- Additional labels for chart objects + labels: {} + # -- Additional labels for chart pods + podLabels: {} + + images: + # -- Changes the registry where to get the images. Useful when there is an internal image cache/proxy + registry: "" + # -- Set secrets to be able to fetch images + pullSecrets: [] + + serviceAccount: + # -- Add these annotations to the service account we create + annotations: {} + # -- Configures if the service account should be created or not + create: + # -- Change the name of the service account. This is honored if you disable on this chart the creation of the service account so you can use your own + name: + + # -- (bool) Sets pod's hostNetwork + # @default -- false + hostNetwork: + # -- Sets pod's dnsConfig + dnsConfig: {} + + # -- Sets pod's priorityClassName + priorityClassName: "" + # -- Sets security context (at pod level) + podSecurityContext: {} + # -- Sets security context (at container level) + containerSecurityContext: {} + + # -- Sets pod/node affinities + affinity: {} + # -- Sets pod's node selector + nodeSelector: {} + # -- Sets pod's tolerations to node taints + tolerations: [] + + # -- Adds extra attributes to the cluster and all the metrics emitted to the backend + customAttributes: {} + + # -- (bool) Reduces number of metrics sent in order to reduce costs + # @default -- false + lowDataMode: + + # -- (bool) In each integration it has different behavior. See [Further information](#values-managed-globally-3) but all aims to send less metrics to the backend to try to save costs | + # @default -- false + privileged: + + # -- (bool) Must be set to `true` when deploying in an EKS Fargate environment + # @default -- false + fargate: + + # -- Configures the integration to send all HTTP/HTTPS request through the proxy in that URL. The URL should have a standard format like `https://user:password@hostname:port` + proxy: "" + + # -- (bool) Send the metrics to the staging backend. Requires a valid staging license key + # @default -- false + nrStaging: + fedramp: + # fedramp.enabled -- (bool) Enables FedRAMP + # @default -- false + enabled: + + # -- (bool) Sets the debug logs to this integration or all integrations if it is set globally + # @default -- false + verboseLog: + + +# To add values to the subcharts. Follow Helm's guide: https://helm.sh/docs/chart_template_guide/subcharts_and_globals + +# If you wish to monitor services running on Kubernetes you can provide integrations +# configuration under `integrations_config` that it will passed down to the `newrelic-infrastructure` chart. +# +# You just need to create a new entry where the "name" is the filename of the configuration file and the data is the content of +# the integration configuration. The name must end in ".yaml" as this will be the +# filename generated and the Infrastructure agent only looks for YAML files. +# +# The data part is the actual integration configuration as described in the spec here: +# https://docs.newrelic.com/docs/integrations/integrations-sdk/file-specifications/integration-configuration-file-specifications-agent-v180 +# +# In the following example you can see how to monitor a Redis integration with autodiscovery +# +# +# newrelic-infrastructure: +# integrations: +# nri-redis-sampleapp: +# discovery: +# command: +# exec: /var/db/newrelic-infra/nri-discovery-kubernetes --tls --port 10250 +# match: +# label.app: sampleapp +# integrations: +# - name: nri-redis +# env: +# # using the discovered IP as the hostname address +# HOSTNAME: ${discovery.ip} +# PORT: 6379 +# labels: +# env: test diff --git a/charts/speedscale/speedscale-operator/2.2.606/.helmignore b/charts/speedscale/speedscale-operator/2.2.606/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/.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.606/Chart.yaml b/charts/speedscale/speedscale-operator/2.2.606/Chart.yaml new file mode 100644 index 000000000..9aa65e986 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606 +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.606 diff --git a/charts/speedscale/speedscale-operator/2.2.606/LICENSE b/charts/speedscale/speedscale-operator/2.2.606/LICENSE new file mode 100644 index 000000000..b78723d62 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/README.md b/charts/speedscale/speedscale-operator/2.2.606/README.md new file mode 100644 index 000000000..6ca25eed9 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/app-readme.md b/charts/speedscale/speedscale-operator/2.2.606/app-readme.md new file mode 100644 index 000000000..6ca25eed9 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/questions.yaml b/charts/speedscale/speedscale-operator/2.2.606/questions.yaml new file mode 100644 index 000000000..29aee3895 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/NOTES.txt b/charts/speedscale/speedscale-operator/2.2.606/templates/NOTES.txt new file mode 100644 index 000000000..cabb59b17 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/admission.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/admission.yaml new file mode 100644 index 000000000..301748a61 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/templates/admission.yaml @@ -0,0 +1,209 @@ +{{- $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 + - apiGroups: + - argoproj.io + apiVersions: + - "*" + operations: + - CREATE + - UPDATE + - DELETE + resources: + - rollouts + 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.606/templates/configmap.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/configmap.yaml new file mode 100644 index 000000000..04dfda91a --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/templates/configmap.yaml @@ -0,0 +1,43 @@ +--- +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 }} + TEST_PREP_TIMEOUT: {{ .Values.operator.test_prep_timeout }} + CONTROL_PLANE_TIMEOUT: {{ .Values.operator.control_plane_timeout }} diff --git a/charts/speedscale/speedscale-operator/2.2.606/templates/crds/trafficreplays.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/crds/trafficreplays.yaml new file mode 100644 index 000000000..aea331547 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/templates/crds/trafficreplays.yaml @@ -0,0 +1,525 @@ +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. Set to + "none" to leave resources in the state they were during the replay. The + default mode "inventory" will revert the environment to the state it was + before the replay. + 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 specifies a custom URL to send *ALL* traffic to. Use + Workload.CustomURI to send traffic to a specific URL for only that + workload. + 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. Required when defining for a test unless a + custom URI is provided. Always required when defining mocks. + 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. This is required if a Ref is not specified. + type: string + inTrafficKey: + description: 'DEPRECATED: use Tests' + 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. This is required unless a CustomURI is specified. + 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. Required when defining for a test unless a + custom URI is provided. Always required when defining mocks. + 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.606/templates/deployments.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/deployments.yaml new file mode 100644 index 000000000..e5f329257 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/hooks.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/hooks.yaml new file mode 100644 index 000000000..3e8231f19 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/rbac.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/rbac.yaml new file mode 100644 index 000000000..e1ea42d99 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/secrets.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/secrets.yaml new file mode 100644 index 000000000..1fb6999e4 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/services.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/services.yaml new file mode 100644 index 000000000..f9da2c25c --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/templates/tls.yaml b/charts/speedscale/speedscale-operator/2.2.606/templates/tls.yaml new file mode 100644 index 000000000..4a2456288 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/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.606/values.yaml b/charts/speedscale/speedscale-operator/2.2.606/values.yaml new file mode 100644 index 000000000..9609117a3 --- /dev/null +++ b/charts/speedscale/speedscale-operator/2.2.606/values.yaml @@ -0,0 +1,138 @@ +# 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.606 + 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 + # how long to wait for the SUT to become ready + test_prep_timeout: 10m + # timeout for deploying & upgrading control plane components + control_plane_timeout: 5m + + +# 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 fd4c6503e..3597376fe 100644 --- a/index.yaml +++ b/index.yaml @@ -273,6 +273,42 @@ entries: - assets/amd/amd-gpu-0.9.0.tgz version: 0.9.0 artifactory-ha: + - annotations: + artifactoryServiceVersion: 7.98.6 + 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 + metadataVersion: 7.90.0 + observabilityVersion: 1.31.5 + apiVersion: v2 + appVersion: 7.98.7 + created: "2024-10-30T00:36:08.622603156Z" + 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. + digest: 2b910211f2dfa7b56434eba4339451ed39b7d6f8245211145b452e4d6d1afe97 + 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.98.7.tgz + version: 107.98.7 - annotations: artifactoryServiceVersion: 7.90.21 catalog.cattle.io/certified: partner @@ -1742,6 +1778,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.98.7 + created: "2024-10-30T00:36:09.065900941Z" + dependencies: + - name: artifactory + repository: file://charts/artifactory + version: 107.98.7 + description: JFrog Container Registry + digest: 4ecab61742ac79212fb221c8006cb40b1a2f02501b4d5db98c35c94359954142 + 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.98.7.tgz + version: 107.98.7 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: JFrog Container Registry @@ -29821,6 +29891,95 @@ entries: - assets/f5/nginx-ingress-1.0.2.tgz version: 1.0.2 nri-bundle: + - annotations: + catalog.cattle.io/certified: partner + catalog.cattle.io/display-name: New Relic + catalog.cattle.io/release-name: nri-bundle + apiVersion: v2 + created: "2024-10-30T00:36:10.96702691Z" + dependencies: + - condition: infrastructure.enabled,newrelic-infrastructure.enabled + name: newrelic-infrastructure + repository: https://newrelic.github.io/nri-kubernetes + version: 3.35.0 + - condition: prometheus.enabled,nri-prometheus.enabled + name: nri-prometheus + repository: https://newrelic.github.io/nri-prometheus + version: 2.1.19 + - condition: newrelic-prometheus-agent.enabled + name: newrelic-prometheus-agent + repository: https://newrelic.github.io/newrelic-prometheus-configurator + version: 1.15.0 + - condition: webhook.enabled,nri-metadata-injection.enabled + name: nri-metadata-injection + repository: https://newrelic.github.io/k8s-metadata-injection + version: 4.22.0 + - condition: metrics-adapter.enabled,newrelic-k8s-metrics-adapter.enabled + name: newrelic-k8s-metrics-adapter + repository: https://newrelic.github.io/newrelic-k8s-metrics-adapter + version: 1.12.0 + - condition: ksm.enabled,kube-state-metrics.enabled + name: kube-state-metrics + repository: https://prometheus-community.github.io/helm-charts + version: 5.26.0 + - condition: kubeEvents.enabled,nri-kube-events.enabled + name: nri-kube-events + repository: https://newrelic.github.io/nri-kube-events + version: 3.11.0 + - condition: logging.enabled,newrelic-logging.enabled + name: newrelic-logging + repository: https://newrelic.github.io/helm-charts + version: 1.23.1 + - condition: newrelic-pixie.enabled + name: newrelic-pixie + repository: https://newrelic.github.io/helm-charts + version: 2.1.6 + - condition: k8s-agents-operator.enabled + name: k8s-agents-operator + repository: https://newrelic.github.io/k8s-agents-operator + version: 0.16.1 + - alias: pixie-chart + condition: pixie-chart.enabled + name: pixie-operator-chart + repository: https://pixie-operator-charts.storage.googleapis.com + version: 0.1.6 + - condition: newrelic-infra-operator.enabled + name: newrelic-infra-operator + repository: https://newrelic.github.io/newrelic-infra-operator + version: 2.12.0 + description: Groups together the individual charts for the New Relic Kubernetes + solution for a more comfortable deployment. + digest: 32c9b1f8f816d19aa3a28177c75cc1af0a12bcc9ebfac1e31940cac5aed559b0 + home: https://github.com/newrelic/helm-charts + icon: file://assets/icons/nri-bundle.svg + keywords: + - infrastructure + - newrelic + - monitoring + maintainers: + - name: juanjjaramillo + url: https://github.com/juanjjaramillo + - name: csongnr + url: https://github.com/csongnr + - name: dbudziwojskiNR + url: https://github.com/dbudziwojskiNR + name: nri-bundle + sources: + - https://github.com/newrelic/nri-bundle/ + - https://github.com/newrelic/nri-bundle/tree/master/charts/nri-bundle + - https://github.com/newrelic/nri-kubernetes/tree/master/charts/newrelic-infrastructure + - https://github.com/newrelic/nri-prometheus/tree/master/charts/nri-prometheus + - https://github.com/newrelic/newrelic-prometheus-configurator/tree/master/charts/newrelic-prometheus-agent + - https://github.com/newrelic/k8s-metadata-injection/tree/master/charts/nri-metadata-injection + - https://github.com/newrelic/newrelic-k8s-metrics-adapter/tree/master/charts/newrelic-k8s-metrics-adapter + - https://github.com/newrelic/nri-kube-events/tree/master/charts/nri-kube-events + - https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-logging + - https://github.com/newrelic/helm-charts/tree/master/charts/newrelic-pixie + - https://github.com/newrelic/newrelic-infra-operator/tree/master/charts/newrelic-infra-operator + - https://github.com/newrelic/k8s-agents-operator/tree/master/charts/k8s-agents-operator + urls: + - assets/new-relic/nri-bundle-5.0.98.tgz + version: 5.0.98 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: New Relic @@ -39172,6 +39331,37 @@ entries: - assets/redpanda/redpanda-4.0.33.tgz version: 4.0.33 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.606 + created: "2024-10-30T00:36:11.698710064Z" + description: Stress test your APIs with real world scenarios. Collect and replay + traffic without scripting. + digest: c828990ba65d75f2d73073bf721104212b409fee79844778f32bdeee321b3001 + 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.606.tgz + version: 2.2.606 - annotations: catalog.cattle.io/certified: partner catalog.cattle.io/display-name: Speedscale Operator @@ -46477,4 +46667,4 @@ entries: urls: - assets/netfoundry/ziti-host-1.5.1.tgz version: 1.5.1 -generated: "2024-10-29T00:36:48.562781565Z" +generated: "2024-10-30T00:36:06.636673735Z"